> ## Documentation Index
> Fetch the complete documentation index at: https://docs.prodbreak.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Deck

> Reference for the Deck pack — objects, events, errors, and formats, faithful to Deck API v2.0.0 — plus the thin prod-break control layer.

<Info>
  Faithful to the live **Deck API v2.0.0** (captured 2026-06-09). The object shapes, event names, error
  envelope, IDs and formats below are exactly what real Deck returns — this page is the **vocabulary to
  assert against**, not a tutorial on Deck. If you know the API, the only new surface is the
  [control layer](#control-prod-break) at the bottom.
</Info>

```bash theme={null}
npx prod-break run deck
# url=http://localhost:8801   key=pbw_3f9a…   pack=deck@0.1.0
# → point your Deck client's base URL at the url; everything else is unchanged
```

Deck ships no SDK and no sandbox, so your app already points a plain HTTP client at
`https://api.deck.co/v2` — repointing it is a one-line base-URL change ([Interception](/interception)).

## Objects

```
agent ──owns──▶ task ──run──▶ task_run ──in──▶ session
                 │               │
        input/output schema   credential ─▶ source (a website)
                                 │
                          event ─▶ event_destination
```

<Tabs>
  <Tab title="task">
    ```jsonc theme={null}
    {
      "id": "task_P8TAk2", "object": "task", "agent_id": "agt_TkKr",
      "name": "Post item on Marketplace",
      "status": "live",                 // learning | test | live
      "input_schema":  { "type": "object", "properties": { "title": { "type": "string" }, "price": { "type": "number" } } },
      "output_schema": { "type": "object", "properties": { "listing_id": { "type": "string" }, "listing_url": { "type": "string" } } }
    }
    ```
  </Tab>

  <Tab title="task_run">
    ```jsonc theme={null}
    {
      "id": "trun_8a2", "object": "task_run",
      "task_id": "task_P8TAk2", "agent_id": "agt_TkKr",
      "credential_id": "cred_alice", "session_id": "sess_m25", "workflow_id": null,
      "status": "completed",            // queued|running|interaction_required|review_required|completed|failed|cancelling|canceled
      "result": "success",              // success | failure | unknown   (null until terminal)
      "runtime_ms": 60310,
      "output": { "listing_id": "lst_77c", "listing_url": "https://www.autotrader.ca/a/lst_77c" },
      "errors": [], "interaction": null,
      "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:01:00Z"
    }
    ```

    `input` / `storage` / `artifacts` are omitted unless requested with `?include=input,storage,artifacts`.
  </Tab>

  <Tab title="session">
    ```jsonc theme={null}
    {
      "id": "sess_m25", "object": "session",
      "status": "idle",                 // queued|running|idle|completing|completed|failed
      "runtime_ms": 716000,
      "created_at": "2026-01-01T00:00:00Z"
    }
    ```
  </Tab>

  <Tab title="event">
    ```jsonc theme={null}
    {
      "id": "evt_v0h", "object": "event",
      "type": "task_run.completed",     // object.action
      "data": { /* subset snapshot of the resource at emit time */ },
      "created_at": "2026-01-01T00:01:00Z"
    }
    ```
  </Tab>

  <Tab title="event_destination">
    ```jsonc theme={null}
    {
      "id": "evtd_1", "object": "event_destination",
      "type": "webhook",                // webhook|aws_sqs|aws_kinesis|aws_s3|aws_eventbridge|gcp_pubsub|rabbitmq|azure_servicebus|hookdeck
      "events": ["task_run.completed", "task_run.failed"],
      "status": "active",               // pending_verification | active | inactive
      "type_config": { "url": "https://you.example/hook", "secret": "whsec_…" }
    }
    ```
  </Tab>
</Tabs>

## Events

`object.action`. `data` is a frozen subset snapshot, and it differs from the REST view: empty fields
serialize as `""` (not `null`) and some fields are dropped — `workflow_id` is the canonical divergence
(`null` over REST, `""` in the event).

| Resource            | Event types                                                                                                           |
| ------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `agent`             | `created` · `updated` · `deleted`                                                                                     |
| `source`            | `created` · `updated` · `deleted`                                                                                     |
| `credential`        | `created` · `unverified` · `verified` · `invalid` · `deleted`                                                         |
| `session`           | `queued` · `running` · `idle` · `completing` · `completed` · `failed`                                                 |
| `task`              | `created` · `updated` · `reset` · `deleted`                                                                           |
| `task_run`          | `queued` · `running` · `interaction_required` · `review_required` · `completed` · `failed` · `canceling` · `canceled` |
| `storage`           | `created`                                                                                                             |
| `event_destination` | `activated` · `deactivated`                                                                                           |
| `user`              | `created`                                                                                                             |

<Note>
  `task_run.**canceling**` is spelled with one L in the event name, while the run **status** is `cancelling`
  (two L). `/event-types` is the only unpaginated list. Deliveries are signed with the
  [Standard Webhooks](https://www.standardwebhooks.com) scheme (`webhook-id` / `webhook-timestamp` /
  `webhook-signature`).
</Note>

## Errors

The `{ type, code, message, field? }` element is the same everywhere, but it surfaces in **two places** —
which one you get depends on whether the *call* failed or the *run* failed.

**1 · A call fails → HTTP error response.** Status `401/403/404/409/422/429/500`, body is the envelope:

```jsonc theme={null}
// e.g. GET /tasks/task_xxx with a bad key  → HTTP 401
{ "errors": [ { "type": "request", "code": "api_key_invalid", "message": "The API key is not valid.", "field": null } ],
  "request_id": "req_v0hE8IKbqoap" }
```

| code                                             | HTTP |
| ------------------------------------------------ | ---- |
| `request` / `api_key_invalid`                    | 401  |
| `request` / `task_not_found` (any `*_not_found`) | 404  |
| `idempotency` / `idempotency_error`              | 409  |
| `rate_limit` / `rate_limit_exceeded`             | 429  |

`400` validation errors are the one exception — a **different**, RFC-9110-style shape (not the envelope):

```jsonc theme={null}
{ "type": "…", "title": "Invalid input", "status": 400,
  "errors": { "input.price": ["must be a number"] }, "traceId": "…" }
```

Quirks: an **unknown route** returns `404` with an **empty body** (no envelope); a bad filter value or
over-range `Limit` is **lenient → `200`**, never `400`.

**2 · A run fails → the call succeeds (HTTP 200), the error lives inside the run.** `result: "failure"`,
`output: null`, and `errors[]` holds the same element:

```jsonc theme={null}
// GET /task-runs/trun_8a2  → HTTP 200
{ "id": "trun_8a2", "status": "failed", "result": "failure", "output": null,
  "errors": [ { "type": "task", "code": "blocked", "message": "Site blocked automated access." } ] }
```

| code                                                 | When the run fails because…                 |
| ---------------------------------------------------- | ------------------------------------------- |
| `task` / `blocked`                                   | the site blocked automated access           |
| `task` / `task_failed`                               | the run failed                              |
| `task` / `timeout`                                   | it exceeded the 30-min run timeout          |
| `task` / `task_result_unknown`                       | the outcome couldn't be determined          |
| `auth` / `auth_invalid`                              | the credentials were rejected by the source |
| `source` / `source_blocked` · `source_not_available` | the source itself failed                    |

**`type` taxonomy** (full set): `api` · `auth` · `idempotency` · `interaction` · `organization` ·
`rate_limit` · `request` · `session` · `source` · `storage` · `task`. Note `api_key_invalid` is type
`request`, **not** `auth` — `auth` is only for credential-vs-source failures.

## Pinnable values

The fields that originate *outside* Deck's logic — the only ones you supply. Everything else (`status`,
`result`, `runtime_ms`, ids, timestamps) is engine-derived and unfakeable.

| Path             | What                                                                  | Binds at               | `null` when            |
| ---------------- | --------------------------------------------------------------------- | ---------------------- | ---------------------- |
| `output`         | run result body (shape = task's `output_schema`)                      | `completed`            | run fails / cancels    |
| `output.<field>` | one output field                                                      | `completed`            | agent couldn't fill it |
| `errors`         | failed run's `errors[]`                                               | `failed`               | success arm → `[]`     |
| `interaction`    | mid-run challenge (`mfa` / `security_question` / `account_selection`) | `interaction_required` | not paused → `null`    |
| `result`         | `success` / `failure` / `unknown`                                     | terminal               | non-terminal → `null`  |

## Formats & conventions

|              |                                                                                            |
| ------------ | ------------------------------------------------------------------------------------------ |
| IDs          | lowercase prefix + **16-char base62** suffix (`trun_Ijz17mHzBrps2bt0`), immutable          |
| Credentials  | only `username` reads back; secrets are write-only                                         |
| Pagination   | `{ data[], has_more, next_cursor, request_id }`; param `Limit` (capital L), clamped at 100 |
| Auth         | `Authorization: Bearer sk_live_…` (sandbox: the printed `pbw_…` key)                       |
| Content-Type | `application/json; charset=utf-8`                                                          |

## Control (prod-break)

The only surface that isn't real Deck. Everything above is the vendor's; this is how you make a given
outcome happen on demand.

```ts theme={null}
// SETUP — seed your create-once definitions (agents/tasks/sources/credentials); runs reference them by id.
await sandbox.seed("tasks", [{ id: "task_P8TAk2", agent_id: "agt_TkKr", status: "live",
  output_schema: { type: "object", properties: { listing_id: { type: "string" }, listing_url: { type: "string" } } } }]);

// pin an out-of-control value, or force the failure arm
await sandbox.exogenous.on("task_run", "output", { listing_id: "lst_77c", listing_url: "https://…/lst_77c" });
await sandbox.exogenous.on("task_run", "branch", "failed");

// trigger a world event (the site intervened); the matching event + run fields fall out
await sandbox.world.trigger("task_run.interaction_required", { task_run_id: "trun_8a2", interaction: { type: "mfa", /* … */ } });

// arm an inbound API fault — your call gets Deck's real envelope above
await sandbox.faults.arm("task_runs", "create", { status: 401, code: "api_key_invalid" });

// advance the virtual clock — due lifecycle events drain in order
await sandbox.clock.advance("60s");
```

<Note>
  * **Event names and behavior are pack-declared** — the engine is generic; the Deck vocabulary above lives
    in this pack.
  * **Control calls hit `/__admin__/*`.** Their errors (e.g. a `task_run_id` that doesn't exist → `409`) go to
    **your test**, not the app under test, and are *not* Deck's error envelope. `sandbox.world.next()` lists
    the world events whose preconditions currently hold.
  * **No magic test inputs** — Deck has none; force outcomes through this surface, never reserved input values.
  * Concepts: [exogenous values](/concepts/exogenous) · [force a branch](/concepts/branching) ·
    [world events](/concepts/causes-not-effects) · [the clock](/concepts/clock) · [HTTP API](/reference/wire).
</Note>

Pinned against **Deck API v2.0.0 (2026-06-09)**, guarded by [contract tests](/fidelity) that diff the pack
against captured live responses.
