Wire Protocol
Errors & sequencing
Every error string the wire returns, and the ordering guarantees you can rely on.
Error shape
Channel replies — phx_reply with status: "error":
{ "status": "error", "response": { "reason": "<string>" } }HTTP — capability issuer, chat REST, dashboard:
{ "errors": [{ "status": "401", "title": "Unauthorized", "detail": "..." }] }HTTP — Durable Objects operation failures:
{ "error": "<string>" }Channel join errors
reason | Channel | Cause |
|---|---|---|
invalid token | any | capability token bad, expired, wrong signature, or bound to a different socket |
forbidden: missing channel:subscribe:{channel} | PubSub | token doesn't grant subscribe on this channel |
forbidden: missing object:read:{type}/{key} | Durable Object | token doesn't grant read on this object |
forbidden: missing chat:subscribe:{threadId} | Chat | token doesn't grant subscribe on this thread |
auth required for private/presence channels | PubSub | private-* / presence-* join with no auth |
auth required for durable object channels | Durable Object | no auth and no app_secret |
auth required | Chat | app has enable_user_authentication = 1 and no token |
invalid app_secret | Durable Object | wrong app_secret |
invalid secret | Chat | agent join with a wrong secret |
app_key mismatch | any | topic's {appKey} segment ≠ the connected app |
invalid topic format | any | malformed topic |
Channel push errors
reason | Push | Cause |
|---|---|---|
Client messages are disabled | PubSub publish | app has Enable Client Messages off |
Channel mode does not permit publishing | PubSub publish | publishing on a channel whose token/mode forbids it |
Rate limit exceeded | PubSub publish | over max_client_events_per_sec |
Validation failed: {detail} | PubSub publish | payload failed channel validation (name / size limits) |
forbidden: missing {verb} | Chat push | principal lacks the capability the push needs |
not_found | Chat cancel | runId not in this thread |
already_ended | Chat cancel / agent run:end | run is already terminal |
forbidden | Chat cancel | not the run owner, and no publish:cancel_any |
invalid_state | agent run:suspend / run:resume | run not in the required prior state |
archive_unavailable | Chat load_older | an archived page couldn't be fetched |
writes must go through the server SDK | Durable Object channel | any client push (the channel is read-only) |
HTTP errors
| Status | Where | Cause |
|---|---|---|
401 | any server call | missing / wrong X-App-Key / X-App-Secret, or the path app_id isn't the authenticated app |
422 | POST /api/auth | missing socket_id / client_id, empty capability list, or a malformed capability string |
422 | Durable Object op | increment on a non-numeric slot, append on a non-list, bad transaction op |
404 | Durable Object | route not matched, or GET /{slot} for an unset slot |
429 | trigger / publish | over max_backend_events_per_sec |
Sequencing guarantees
Cursors
| Product | Cursor | Monotonic | Total order |
|---|---|---|---|
| PubSub | serial (per channel) | yes | yes, within a channel |
| Durable Objects | log_id (per object) | yes | yes, across all subscribers |
| Chat | log_id (per thread) | yes | yes, across all subscribers |
| Spaces | — | — | no ordering; pure relay |
Guarantees
- A
snapshotreflects state exactly at itslog_id. Live events resume atlog_id + 1— nothing between is dropped or duplicated. after_serial/after_log_idreplay is exact and in order, delivered before the snapshot (Durable Objects, Chat) or before live delivery (PubSub).- Chat:
run:endis always emitted before its companionbatch. The GC compactionbatchis atomic — all four slot changes arrive together. - Durable Objects: a
batchfromset_all/transactionis atomic. - PubSub: message order within a channel matches publish order; across channels there is no ordering.
Not guaranteed
- Ordering of events across different channels / objects / threads on one socket.
- Delivery of Spaces events missed while disconnected.
- Live
presence:join/presence:leaveonephemeral-*channels (not yet broadcast — see Spaces).