diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,18 @@
 
 ## Unreleased
 
+## 0.14.0.0 — 2026-08-21
+
+### Breaking Changes
+
+- Requires the lockstep `keiro ^>=0.14.0.0`, `keiro-pgmq ^>=0.14.0.0`, and
+  `keiro-migrations ^>=0.14.0.0`.
+
+### New Features
+
+- `outbox list` accepts `--status rejected` and renders `rejected_at`,
+  `rejection_code`, and bounded `rejection_detail` in human and JSON output.
+
 ## 0.13.0.0 — 2026-08-17
 
 ### Breaking Changes
diff --git a/keiro-ops.cabal b/keiro-ops.cabal
--- a/keiro-ops.cabal
+++ b/keiro-ops.cabal
@@ -1,6 +1,6 @@
 cabal-version:   3.0
 name:            keiro-ops
-version:         0.13.0.0
+version:         0.14.0.0
 synopsis:        Operational command-line interface for Keiro deployments
 description:
   A standalone database-only console and embeddable command tree for inspecting
@@ -68,9 +68,9 @@
     , effectful             >=2.6       && <2.7
     , effectful-core        >=2.6       && <2.7
     , hasql                 >=1.10      && <1.11
-    , keiro                 ^>=0.13.0.0
-    , keiro-migrations      ^>=0.13.0.0
-    , keiro-pgmq            ^>=0.13.0.0
+    , keiro                 ^>=0.14.0.0
+    , keiro-migrations      ^>=0.14.0.0
+    , keiro-pgmq            ^>=0.14.0.0
     , kiroku-store          >=0.8       && <0.9
     , optparse-applicative  >=0.18      && <0.20
     , text                  >=2.1       && <2.2
@@ -104,9 +104,9 @@
     , hasql                 >=1.10      && <1.11
     , hasql-transaction     >=1.1       && <1.3
     , hspec                 >=2.11
-    , keiro                 ^>=0.13.0.0
+    , keiro                 ^>=0.14.0.0
     , keiro-ops
-    , keiro-pgmq            ^>=0.13.0.0
+    , keiro-pgmq            ^>=0.14.0.0
     , keiro-test-support
     , kiroku-store          >=0.8       && <0.9
     , optparse-applicative  >=0.18      && <0.20
diff --git a/src/Keiro/Ops/Outbox.hs b/src/Keiro/Ops/Outbox.hs
--- a/src/Keiro/Ops/Outbox.hs
+++ b/src/Keiro/Ops/Outbox.hs
@@ -70,7 +70,7 @@
       List
         <$> ( ListOptions
                 <$> textOption "source" "SOURCE" "Producing bounded-context source"
-                <*> optional (option statusReader (long "status" <> metavar "STATUS" <> help "pending, publishing, sent, failed, or dead"))
+                <*> optional (option statusReader (long "status" <> metavar "STATUS" <> help "pending, publishing, sent, rejected, failed, or dead"))
                 <*> optional (textOption "destination" "DESTINATION" "Destination filter")
                 <*> option positiveIntReader (long "limit" <> metavar "N" <> Opt.value 100 <> showDefault <> help "Maximum rows")
             )
@@ -184,7 +184,7 @@
 outboxListResult :: [OutboxRow] -> OpsResult
 outboxListResult outboxRows =
   OpsResult
-    { headers = ["id", "source", "destination", "status", "attempts", "created_at", "last_error"],
+    { headers = ["id", "source", "destination", "status", "attempts", "created_at", "last_error", "rejected_at", "rejection_code", "rejection_detail"],
       rows = map outboxRow outboxRows,
       jsonValue = Aeson.toJSON (map outboxJson outboxRows)
     }
@@ -197,7 +197,10 @@
     statusText row.status,
     showText row.attemptCount,
     timeText row.createdAt,
-    maybe "" (truncateCell 120) row.lastError
+    maybe "" (truncateCell 120) row.lastError,
+    maybe "" timeText row.rejectedAt,
+    maybe "" publishRejectionCode row.rejection,
+    maybe "" (truncateCell 120) (row.rejection >>= publishRejectionDetail)
   ]
 
 outboxJson :: OutboxRow -> Value
@@ -218,6 +221,9 @@
       "next_attempt_at" .= row.nextAttemptAt,
       "last_error" .= row.lastError,
       "published_at" .= row.publishedAt,
+      "rejected_at" .= row.rejectedAt,
+      "rejection_code" .= fmap publishRejectionCode row.rejection,
+      "rejection_detail" .= (row.rejection >>= publishRejectionDetail),
       "created_at" .= row.createdAt,
       "updated_at" .= row.updatedAt
     ]
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -135,6 +135,7 @@
       isParseSuccess (parseOps embeddedHooks ["rebuild", "external-read", "counter_reader", "1"]) `shouldBe` True
       isParseSuccess (parseOps embeddedHooks ["rebuild", "retire-external-read", "counter_reader", "1"]) `shouldBe` True
       isParseSuccess (parseOps embeddedHooks ["rebuild", "reproject-stream", "ops-group", "ops-projection", "orders-1"]) `shouldBe` True
+      isParseSuccess (parseOps embeddedHooks ["outbox", "list", "--source", "ops-source", "--status", "rejected"]) `shouldBe` True
       isParseFailure (parseOps embeddedHooks ["rebuild", "retire-external-read", "counter_reader", "0"]) `shouldBe` True
       isParseFailure (parseOps embeddedHooks ["rebuild", "reproject-stream", "ops-group", "ops-projection", "orders-1", "--page-size", "0"]) `shouldBe` True
 
@@ -1028,6 +1029,48 @@
       applied <- OpsOutbox.runCommand (opsEnv True store) (OpsOutbox.RequeueStuck 0 10)
       applied `shouldSatisfy` isSucceeded
       outboxStatus store outboxId `shouldReturn` Just Outbox.OutboxFailed
+
+    it "filters and renders rejected rows with bounded audit fields" $ \store -> do
+      now <- getCurrentTime
+      let outboxId = testOutboxId "018f5f43-8a70-7b9a-9a9b-59d391a76811"
+          event = sampleIntegrationEvent now "outbox-rejected-message"
+      rejection <-
+        either (fail . show) pure $
+          Outbox.mkPublishRejection "authorization.denied" (Just "the sink policy refused this message")
+      expectStore store (runTransaction (Outbox.enqueueOutboxTx (Outbox.OutboxMessage outboxId event)))
+      summary <-
+        expectStore store $
+          Outbox.publishClaimedOutbox
+            (\rows -> pure [(row.outboxId, Outbox.PublishRejected rejection) | row <- rows])
+            Outbox.defaultPublishOptions
+            Nothing
+      summary.rejected `shouldBe` 1
+
+      listed <-
+        OpsOutbox.runCommand
+          (opsEnv False store)
+          ( OpsOutbox.List
+              OpsOutbox.ListOptions
+                { source = "ops-source",
+                  status = Just Outbox.OutboxRejected,
+                  destination = Nothing,
+                  limit = 10
+                }
+          )
+      humanField "status" listed `shouldBe` Just "rejected"
+      humanField "rejection_code" listed `shouldBe` Just "authorization.denied"
+      humanField "rejection_detail" listed `shouldBe` Just "the sink policy refused this message"
+      humanField "rejected_at" listed `shouldSatisfy` maybe False (not . Text.null)
+      case listed of
+        Succeeded OpsResult {jsonValue = Aeson.Array values} ->
+          case values Vector.!? 0 of
+            Just (Aeson.Object row) -> do
+              KeyMap.lookup "status" row `shouldBe` Just (Aeson.String "rejected")
+              KeyMap.lookup "rejection_code" row `shouldBe` Just (Aeson.String "authorization.denied")
+              KeyMap.lookup "rejection_detail" row `shouldBe` Just (Aeson.String "the sink policy refused this message")
+              KeyMap.lookup "rejected_at" row `shouldSatisfy` maybe False (/= Aeson.Null)
+            other -> expectationFailure ("expected one rejected JSON object, got " <> show other)
+        other -> expectationFailure ("expected a successful rejected-row listing, got " <> show other)
 
     it "surfaces dispatch dead letters through the supported API" $ \store -> do
       let sourceEvent = EventId (testUuid "018f5f43-8a70-7b9a-9a9b-59d391a76802")
