> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.cessy.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.cessy.ai/_mcp/server.

# Runtime API

> Use Runtime REST endpoints for Cessy commands, projections, agent discovery, channel interaction, event import, projection rebuild, and OpenAPI discovery.

The Runtime REST API is generated from a published DomainConfig and includes app-scoped platform endpoints for user-visible channel interaction. The generated OpenAPI contract for the target app is canonical.

## OpenAPI

Fetch the contract:

```http
GET /api/rest/<tenant-id>/<app-id>/openapi
```

The Production OpenAPI document is public metadata. Add
`?workspaceId=<workspace-id>` when the contract should describe a Workspace
runtime source instead; because that response can expose unpublished model
metadata, the Workspace request requires normal app-scoped Runtime bearer
authentication. Use the response to discover endpoint names, schemas, auth
requirements, response shapes, and errors. Do not infer app-specific endpoint
names from another app or mix Production and Workspace contracts.

Workspace OpenAPI and agent discovery can describe agents that exist only in
the selected Workspace. The channel runtime still resolves a new DM target from
the published app configuration, so a Workspace-only agent cannot receive a DM
until that change is promoted.

## Commands

Published commands become REST endpoints. A command request:

1. authenticates the caller
2. checks permissions
3. validates input
4. loads decision state
5. emits events if the command succeeds
6. returns a structured result

Keep cross-command orchestration, lookup-before-create, and cross-app
coordination outside the command request itself. For delivery safety, send an
optional `Idempotency-Key` header containing a client-owned UUID. One key names
one exact command intent:

* Repeating the same command and body with the same key returns the canonical
  committed receipt with `idempotentReplay: true`; it does not dispatch the
  handler or append the command events again.
* Reusing a key with different input returns `409
  COMMAND_IDEMPOTENCY_CONFLICT` and `effectState: rejected`.
* A typed `503 COMMAND_UNAVAILABLE` with `effectState: rolled_back` confirms
  that the request did not append. Start a new intent only if the operator
  still wants the action.
* A lost connection or unclassified `5xx` has an unknown outcome. Retry the
  identical request with the same key until the canonical receipt or an
  authoritative terminal result is returned. Do not switch to a new key while
  the original outcome is unknown.

The `executionId` in the response equals the supplied intent UUID and, together
with `correlationId`, locates the Production or Workspace execution trace.
Idempotency covers transport retries for one intent; business-level dedupe
across distinct intent keys still belongs in the domain or caller workflow.

New receipt-bound commands retain their original scope and decision-boundary
evidence with the committed events. Recovery after a lost commit acknowledgement
returns that recorded decision without running the handler again. Historical
receipts without this context remain readable; Cessy does not invent missing
decision evidence. Conflicting or malformed recorded context fails closed.

A successful command proves the canonical command execution boundary and the
append of any events reported by that execution. It does not prove that every
downstream projection has applied those events. Diagnose projection delivery
separately; a missing projection row is not proof that the command never ran,
and retrying with a new intent can create a second business operation. Reuse the
same idempotency key when the original outcome is unknown. For events
covered by durable runtime intake, Cessy automatically retries a missing
projection phase without replaying the command or appending another source
event.

## Policy fan-out

A `custom_code` policy may return at most 100 ordinary inline commands. A larger
ordinary result is rejected before any child command write begins. Use a
homogeneous durable batch for more than 100 similar commands, or model explicit
durable stages when command types are mixed or the workflow needs a final
completion barrier.

The 10-100 range is an admission ceiling, not an automatic recommendation. Use
inline fan-out in that range only when the complete downstream cascade,
including triggered policies and projection callbacks, is bounded and measured
below 60 seconds at p95 and below the 90-second hard segment regression budget. Prefer
`__batch` even for 100 items or fewer when homogeneous high-volume work needs
operational isolation, independent retries, rate limiting, progress, or
dead-letter handling. For mixed work or final synchronization, model explicit
durable stages now; a reusable durable completion barrier is tracked for future
work in #3005.

Inline fan-out also bounds durable identity by UTF-8 bytes: 1,000 bytes for a
command type and 2,000 bytes each for a resolved scope key or child ID. This
validation covers explicit, command-data, boundary-derived, and trigger-default
scopes before child dispatch. Oversized untrusted identities are represented by
bounded hash-suffixed failure evidence; derived parent prefixes use deterministic
hashes.

Prepared children with the same resolved decision scope execute sequentially in
declaration order. Independent scope groups use a sliding window, so each free
lane starts the next ready scope instead of waiting for a fixed chunk to
complete.

For admitted inline fan-out, runtime persists a complete child evidence census
before the first write and reserves handler time for terminalization. The census
contains stable child identity, index, command type, resolved scope, and status;
it does not contain command payloads or replay intent. Terminal evidence accounts
for every child as `succeeded`, `failed`, `unknown`, or `not_started`. `unknown`
means the child was issued but its append outcome was not confirmed; reconcile it
against stable business or canonical event evidence before retrying. When the
dispatch deadline is reached, runtime records the exhaustive policy failure and,
when `onError` is configured, a leased compensation owner before invoking that
command. The failure becomes terminal only after compensation is durably
completed or explicitly terminated by an audited abort. Operator aborts use
`OPERATOR_ABORTED` only before dispatch starts; terminating a started attempt
retains `DISPATCH_OUTCOME_UNKNOWN`. Recovery may use `WORKSPACE_EXPIRED` or
`WORKSPACE_UNAVAILABLE` when the exact Workspace source definitively no longer
exists. An abort records actor, reason, and timestamp without claiming that the
compensation command ran. This is an evidence contract, not a replayable child
outbox.

If a configured `onError` command cannot be mapped, normalized, or encrypted,
runtime has not created a compensation owner and does not fabricate one merely
to mark it aborted. It first persists a queryable ErrorLog diagnostic with code
`POLICY_FANOUT_COMPENSATION_PREPARATION_FAILED` under a deterministic identity;
only after that write is confirmed may it terminalize the original fan-out
without `compensation`, retaining the fan-out's primary error code. Diagnostic
failure leaves the lifecycle running (or missing before a manifest exists) and
fails closed. The diagnostic contains controlled references and a phase only,
never mapping expressions, command/event data, receipt errors, crypto messages,
or credentials.

For criteria-based child commands, the persisted fan-out cutoff is also the
absolute PostgreSQL statement cutoff. The runtime overrides a shorter pooled
session default for that transaction, then uses the reserved terminal window to
finish rollback and an indexed raw-tag probe while retaining half of that
window for durable terminal lifecycle persistence. Exact canonical rows produce a
successful receipt and are published with their stored identities; a confirmed
rollback with canonical absence produces a failed receipt; unresolved,
unreadable, partial, or mismatched evidence remains unknown. Reconciled receipts
surface only operation class, elapsed time, and reconciliation state, never SQL,
criteria, tags, command data, or event payloads. Production and Workspace probes
remain isolated by their effective app IDs.

Inline fan-out requires a stable trigger event ID. Redelivery before the
persisted deadline reports the execution as already in progress without
reissuing children or running `onError`. At or after the deadline, runtime can
atomically record failure on the same execution without replaying children.
Because a running census is not a per-child checkpoint, interruption recovery
marks every child `unknown`; reconcile canonical evidence before a business
retry. `onError` is a separate durable compensation phase: redelivery waits for
its live lease or claims an expired lease, then checks indexed canonical child
evidence and verifies the encrypted intent's exact materialized-runtime
fingerprint before dispatch. The fingerprint covers full config, including
subscriptions and downstream policies, plus a versioned executor contract. An
append already present after an ambiguous failure is recorded as reconciled
without fabricated execution or event-count evidence.

Before mutable worker execution, the canonical lifecycle pre-read uses the same
deterministic execution ID through a bounded transient-connection retry
sequence. Only a successful read with no lifecycle proves that the execution
has not started. Retry exhaustion, cancellation, deadline expiry, or an
unavailable canonical probe fails closed before worker code or business
`onError` instead of risking duplicate work. Exhaustion does not schedule an
automatic retry job; explicit/source redelivery of the durable event reuses the
same deterministic lifecycle ID and canonical fence.

One process-wide authority admits at most ten covered runtime operations and at
most seven ordinary command executions. One slot remains available for each
missing protected class: lifecycle, projection callback, and durable diagnostic
evidence. Owner-fair queues and bounded control priority allow control work to
progress without starving commands. These are process-local admission ceilings,
not cross-replica or physical database-pool reservations.

Every ordinary command entry point uses this authority, including public API,
batch, `onError`, and compensation execution. Each physical attempt owns its
command slot through handler dispatch, event persistence, and synchronous
projection publication, then releases it before retry backoff or synchronous or
asynchronous post-policy handlers dispatch nested commands. Inline fan-out uses
the same executor-owned admission for every physical attempt; caller-authored
fan-out identity metadata cannot bypass it. If a caller's deadline expires after
work starts, the caller returns promptly but the slot remains occupied until
that physical attempt and synchronous projection work settle.

Async policy queue wait is measured separately and counts toward the supported
end-to-end segment budget. The handler execution deadline starts only after
admission; expiring a queued subscription after its source event was persisted
would silently skip that delivery. Once admitted, worker execution, projection
callbacks, command admission and execution, and terminal persistence share the
handler deadline. Projection callbacks are capped at 30 seconds and below the
remaining handler budget, with cooperative cancellation and a terminal guard.
Structured telemetry separates handler queue wait, central command admission
(`runtime.command_admission.wait_ms`), fan-out command-permit wait, lifecycle
phase/retry timing, diagnostic persistence, and projection-callback timing.
Protected lifecycle diagnostics include pool-acquire/query durations, the
configured deadline, a before/during-query termination phase, and one correlated
recovery outcome after a successful retry. Deterministic lifecycle IDs appear in
that protected diagnostic, never in aggregate metric attributes.

`PolicyExecutionLogged` version 1 uses the optional receipt properties
`operationClass`, `elapsedMs`, and `reconciliationState` for append diagnostics.
Historical version-1 receipts without those properties remain valid. The first
Production release that both reads and persists the expanded shape is commit
`13ce8501d6c98a17491220d22cc207a88f53606b`. That exact release is the permanent
rollback floor for expanded receipts: never deploy or roll back a reader below
it, because earlier strict readers cannot replay expanded version-1 events.

Recovery-loop tracking is independent of ordinary `__cascadeDepth`, so a deep
valid saga can still invoke its first configured `onError`. That command and
its downstream events carry a stable `__onErrorLineage` and
`__onErrorDepth: 1` (plus `__isOnError` for compatibility). Runtime currently
allows one recovery boundary. A later failure in the same lineage does not
launch another `onError`; durable fan-out instead records terminal
fan-out `errorCode: ON_ERROR_SUPPRESSED` evidence. Before that terminal is
committed, ErrorLog durably records reason `RECOVERY_DEPTH_LIMIT_REACHED`, the
target command, lineage, and observed/maximum recovery depths under a stable
diagnostic identity. Execution diagnostics expose the terminal marker
separately from compensation.

The complete immutable `onError` intent is persisted in a bounded, versioned
AES-256-GCM envelope. The global cron tick durably rotates through a bounded
window of authoritative Production and Workspace runtime scopes, then scans the
canonical latest pending lifecycle under each effective app ID. Runtime loading
starts only after an authoritative candidate page contains due compensation;
empty or incomplete pages do not require a published model. Failures loading a
runtime for due work remain visible and leave the compensation pending. It re-reads the
stream and claims expired leases without waiting for the original worker, and
it never replays fan-out children or substitutes Production config for a
Workspace.

If exact Workspace materialization reports typed not-found, an expired or
missing unstarted compensation is terminalized with `WORKSPACE_EXPIRED` or
`WORKSPACE_UNAVAILABLE`. A started attempt is never dispatched again and uses
`DISPATCH_OUTCOME_UNKNOWN`. Transient storage failures, invalid Workspace
config, and missing or incorrect `CREDENTIAL_ENCRYPTION_KEY` material fail
closed and leave compensation pending. Drain or explicitly resolve pending
compensations before rotating that key; pending intent does not currently
support a multi-key or key-ID fallback.

## Batch commands

Batch command execution is useful for controlled automation. A batch declaration
is homogeneous: it contains one `commandType` and many item payloads. It does not
encode a mixed command sequence or guarantee that a final item acts as a
completion barrier. Segment those workflows explicitly and advance the modeled
stage only after the preceding batch reaches a terminal outcome whose progress
and dead-letter evidence satisfies the business completion rule. Use this
operational isolation for homogeneous high-volume work even when it fits below
the 100-command inline ceiling. Continue using explicit staged completion for
mixed/final synchronization; the reusable durable barrier is future work
tracked in #3005.

Keep batches bounded and give target commands stable business identities when
retries must be deduplicated. Batch tracking does not itself make commands
idempotent, promise that the triggering event plus all job/item records share
one atomic transaction, or make successive progress reads an isolated snapshot.
Each processing claim is ownership-fenced. Crash recovery retries only claims
that provably never began execution; a started claim without durable outcome
proof becomes an explicit dead letter and must be reconciled before deliberate
operator replay. A thrown/ambiguous command result is never retried blindly.
Before a replacement processor publishes a running lease, it takes a durable
yielding lease and settles every stale claim from the expired owner. This same
admission boundary applies to an operator resume, so resumed work cannot advance
alongside claims left behind by its previous processor.

Native batch item diagnostics use five payload-free effect states:
`not_started`, `rejected`, `rolled_back`, `committed`, and `outcome_unknown`.
Each new attempt carries a deterministic execution ID and canonical request
hash that activates EventStore idempotency. After a lost response, only a
complete canonical receipt matching the execution ID, request hash, and
command type can settle success. An absent or unavailable receipt remains
unknown, including a zero-event command's lost response. Confirmed rollback
from stream, tag, and criteria appends is retryable.

`get_batch_job` and `list_batch_jobs` expose stored cancelled jobs as
`legacy_cancelled_unsettled`. This means cancellation control is recorded but
there is no immutable terminal census. Pending items and unknown attempts
still require reconciliation; the cancelled flag is not no-effect evidence.

The Design MCP `list_batch_job_items` tool pages stable item IDs/indexes,
technical identity hashes, attempt/category/error fields, execution and decision
receipts, reconciliation references, and replay eligibility without returning
command data, scope values, raw errors, or business event data. Inspection is
fenced to the tenant, effective app, and selected Production or Workspace
source.

For an `outcome_unknown` dead letter, reconcile the execution reference against
canonical command/event/projection evidence. Record the conclusion with
`record_batch_item_effect_verification`, echoing the item hash, replay generation,
attempt, and command execution ID when present: `effect_absent` makes that exact
attempt eligible for replay, while `effect_committed` permanently blocks it. Then call
`replay_dead_letter_items` with exact item IDs and technical identity hashes.
Bulk replay preflights the complete selection and makes no changes if any item
is ambiguous or committed.

For a trusted `custom_code` policy that always returns exactly one homogeneous
batch, set `typeConfig.durableRecovery` to `native_batch_v1` and declare a
finite, non-empty `targetCommands` set. This opt-in binds the completed batch
manifest to the durable source-policy work ID and registers its Production or
Workspace runtime in a dedicated recovery catalog. In the isolated production
topology, a minutely deduplicated runtime-worker producer assigns the manifest
to a long-lived, lease-fenced recovery worker. That owner is independent of Cron
lane deadlines and can honor the full per-item attempt budget; inline-only
deployments retain a fair alternating Cron fallback. The custom handler remains
an opaque, at-most-once boundary:
uncertainty before a complete manifest requires reconciliation, while
post-manifest recovery uses the Batch claim fences described above. Configured
`onError` remains supported.

## Projections

Projection endpoints read current state. They support filtering, sorting, pagination, and output shaping according to the projection contract.

Projection delivery for durable runtime events is recoverable per source
occurrence. Each projection handler atomically commits its document changes and
an exact settlement receipt. If a transient PostgreSQL failure interrupts a
phase, the matching runtime revision reloads the canonical event, skips handlers
that already settled, and retries only the missing projection handlers. It does
not rerun the command or publish post-event policies. Production and each
Workspace use separate recovery and receipt scopes.

Recovery is bounded and fail-closed. Deterministic handler failures, exhausted
retries, an unavailable source row, or an ended Workspace become poisoned
operational state for diagnosis; Cessy does not infer projection success from
elapsed time. A read immediately after command success can therefore still lag
while recovery is pending.

Policy projection callbacks read the exact active Production or Workspace
source and never substitute Production for a Workspace. Their bounded,
value-free diagnostics record both successful and failed callback outcome,
phase, timing, remaining budget, response reach/status, and query shape.
Callbacks also carry the worker's exact runtime-config revision. Invalid app
ownership, unknown projection, stale worker snapshot, and transient app/runtime
registry failures therefore produce distinct retry and reconciliation guidance.

See [Projection Queries](/build-a-backend/projection-queries).

## Agent discovery

Runtime clients can discover the safe agent catalog before opening a chat:

```http
GET /api/rest/<tenant-id>/<app-id>/agents
Authorization: Bearer <runtime-api-key-or-oauth-token>
```

The response contains only stable public metadata:

```json
{
  "agents": [
    {
      "id": "agent-support",
      "name": "support",
      "label": "Support",
      "kind": "standard"
    }
  ]
}
```

Prompts, models, MCP configuration, credentials, container settings, provider
secrets, and private scope configuration are not part of this response. Use the
returned `id` as `agentId` in `POST /channels/dm` only when that agent is already
published. A Workspace-selected catalog can also include preview-only agents;
promote those agents before opening a DM. The catalog uses normal Runtime REST
authentication; the channel call itself has the stricter user-principal
requirement described below.

## Channels

Runtime REST exposes channel endpoints for user-delegated chat experiences,
including partner BFFs and custom frontends. Every channel and user-channel-view
operation requires an OAuth/JWT bearer token that resolves to the authenticated
human user. API keys and agent service tokens are rejected. The endpoints apply
app access checks and only expose channels tied to agents from the target app.

Common channel calls:

```http
POST /api/rest/<tenant-id>/<app-id>/channels/dm
GET /api/rest/<tenant-id>/<app-id>/channels
GET /api/rest/<tenant-id>/<app-id>/channels/<channel-id>
GET /api/rest/<tenant-id>/<app-id>/channels/<channel-id>/messages
POST /api/rest/<tenant-id>/<app-id>/channels/<channel-id>/messages
POST /api/rest/<tenant-id>/<app-id>/channels/<channel-id>/close
POST /api/rest/<tenant-id>/<app-id>/channels/<channel-id>/read
GET /api/rest/<tenant-id>/<app-id>/user-channel-views
```

Every successful `POST /channels/dm` creates a fresh human-to-agent conversation
channel and linked session and returns `201` with `created: true`. Repeating the
call creates another conversation; it does not deduplicate by user and agent.
The supplied `agentId` must exist in the published app configuration even if
the request selects a Workspace runtime source.
To continue a prior conversation, retain its returned channel id or select it
from the user's channel views and post through that channel's messages endpoint.
Use `/channels/{channelId}/read` and `/user-channel-views` for inbox state.

Channel endpoints are not a replacement for modeled commands. Business mutations should still execute commands; channel messages should explain or coordinate the user-visible work.

### Private turn context

`POST /channels/dm` and `POST /channels/{channelId}/messages` accept an optional
`agentTurnContext` object. Use it when the visible user message should remain
natural, but the agent needs private orientation for this turn, such as the
current product surface, focused document id, draft/published status, projection
pointers, write/proposal constraints, or small namespaced `extensions` for
builder-specific hints. `agentSessionContext` is accepted as a compatibility
alias; send only one field.

The context is not returned by channel message APIs and is not copied into the
public channel log. It is injected privately for the associated agent wake and
monitoring evaluation, with `scope: "next_message_only"`.
For `POST /channels/dm`, provide it only when the request also posts `content`;
`createOnly` DM opens do not have an associated turn.

`permissions` inside the context are orientation only. Runtime authorization,
delegation, domain commands, and explicit user action requirements still
control reads and writes.

## Event import

Event import is for migrations, repairs, and controlled operational workflows. Normal user behavior should execute commands.
Ordinary cascade depth (`__cascadeDepth`), recovery markers (`__isOnError`,
`__onErrorDepth`, `__onErrorLineage`), and the internal compatibility marker
`__legacyRecoveryOriginUnknown` are platform-owned metadata and are rejected at
the public import boundary.

## Projection rebuild

Projection rebuild reprocesses event history through a replacement read model
for projection definition changes, migrations, and deliberate bulk repair.
Transient delivery gaps for unchanged durable projections use automatic
occurrence recovery instead. Verify output after a rebuild and communicate
production impact before running it on large event histories.

## Adapter call route

The adapter call route exists for controlled platform workflows. Treat it as platform-managed support, not as the normal public app API. External consumers should use commands, projections, webhooks, subscriptions, or a BFF.