Skip to content

Item protocol

The item transport is a JSON message protocol on /websocket. It carries authentication, subscriptions, and item CRUD and query operations over one persistent connection. It is configured by the WEBSOCKETS_REST_* family (see Manage configuration).

Every message is a single JSON object with a type. A client may attach a uid to any message it sends, and the server echoes that uid on the responses and on the subscription messages it produces, so a client can correlate a stream of messages to the request that started it.

Open a WebSocket to the configured path, using wss:// in production:

wss://example.com/websocket

What happens next depends on the authentication mode. Under strict the token is presented as an Authorization: Bearer header at the upgrade. Under handshake the first frame authenticates. Under public the connection is usable immediately and a token may be supplied later.

{ "type": "auth", "access_token": "<token>" }

The server replies:

{ "type": "auth", "status": "ok" }

A failed authentication returns an error frame with code AUTH_FAILED. Before the connection has authenticated successfully, every rejected credential uses AUTH_FAILED, including an expired one. After the connection has authenticated as a user, an expired token uses TOKEN_EXPIRED, and any other invalid credential still uses AUTH_FAILED. See Authentication for the full per-mode outcomes. Re-authenticate at any time by sending another auth frame with a fresh token.

Subscribe to a collection:

{ "type": "subscribe", "collection": "articles", "uid": "a1" }

Optional fields narrow the subscription:

  • event — one of create, update, delete. Omit to receive create and update.
  • item — a single primary key to watch.
  • query — a filter and field selection, using the query DSL.

The server sends each subscription message as a separate frame. A subscription without an event filter starts with an initial snapshot:

{ "type": "subscription", "event": "init", "data": [{ "id": 41, "title": "Existing" }], "uid": "a1" }

The snapshot data is an array for a collection subscription. For an item-scoped subscription, one that sets item, data is the single item object instead. When the subscription’s query requests meta, the init frame also carries a meta object with the standard query metadata.

Later changes arrive in their own frames:

{ "type": "subscription", "event": "create", "data": [ { "id": 42, "title": "New" } ], "uid": "a1" }
{ "type": "subscription", "event": "update", "data": [ { "id": 42, "title": "Edited" } ], "uid": "a1" }

When an event filter is set, the init message is an acknowledgement with no snapshot ({ "event": "init" }), and only that event follows. Delete messages carry the deleted keys, not item data, and are delivered only to an event: "delete" subscription (see the delete feed):

{ "type": "subscription", "event": "delete", "data": [ 42 ], "uid": "d1" }

Unsubscribe with the same uid:

{ "type": "unsubscribe", "uid": "a1" }

An unsubscribe with a uid ends that one subscription. An unsubscribe with no uid ends every subscription on the connection. Either way the server acknowledges with { "type": "subscription", "event": "unsubscribe" }, echoing the uid when one was sent.

The item transport can run the same CRUD operations as the HTTP items API, using type: "items" with an action. The server replies with a type: "items" message carrying the result under data.

Create:

{ "type": "items", "collection": "articles", "action": "create", "data": { "title": "Hello" }, "uid": "c1" }

Read (by id, by ids, or by query):

{ "type": "items", "collection": "articles", "action": "read", "query": { "filter": { "status": { "_eq": "published" } } }, "uid": "r1" }

Update (data is applied to the item or items named by id, ids, or query):

{ "type": "items", "collection": "articles", "action": "update", "id": 42, "data": { "title": "Edited" }, "uid": "u1" }

Delete (by id, ids, or query):

{ "type": "items", "collection": "articles", "action": "delete", "id": 42, "uid": "x1" }

Item operations are permission-checked exactly as the HTTP API is, under the connection’s current role.

Not every message carries a status. An authentication acknowledgement is { "type": "auth", "status": "ok" }. Item results and subscription messages have no status field and carry their data directly. A read result, or a multi-item update, also carries a meta object when the request’s query asks for it. An error is a message with status: "error" and a code:

{ "type": "subscribe", "status": "error", "error": { "code": "DELETE_FEED_FORBIDDEN", "message": "Delete notifications are not available for this subscription." }, "uid": "d1" }

The type on an error is the type of the message that failed, so a client can route it by type and uid. A failure that is not tied to a routable command, such as a malformed frame, an unrecognized message type, or a rate or pending-command rejection, uses the type server.

Most errors are informational and leave the connection open. A malformed frame, for example, returns INVALID_PAYLOAD and the connection keeps serving. The conditions that end the connection are listed under Close codes.

The error codes:

CodeMeaning
AUTH_FAILEDThe supplied token was rejected.
TOKEN_EXPIREDThe supplied token has expired.
INVALID_PAYLOADThe message could not be parsed or failed validation.
FORBIDDENThe request is not permitted. This response does not distinguish an unknown collection from one the connection cannot access.
UNSUPPORTED_MESSAGE_TYPEThe type is not a recognized message type.
REQUESTS_EXCEEDEDThe connection exceeded its message rate limit.
TOO_MANY_PENDINGA command arrived while 10 others were already waiting. The connection then closes with 1013.
SUBSCRIPTION_LIMITThe subscription limit for the connection was reached.
DELETE_FEED_FORBIDDENThe subscription is not eligible for delete notifications.
INTERNAL_ERRORThe request failed for an unexpected reason.

The server sends WebSocket ping control frames at the interval set by WEBSOCKETS_HEARTBEAT_PERIOD. Standard WebSocket clients, including browsers and the ws library, answer them automatically, so no application code is needed. A connection that misses two heartbeat periods without a pong is closed.

Admission happens at the HTTP upgrade, before the socket opens, so a connection refused for a disallowed origin, a query-string token, an exhausted rate-limit budget, or unavailable transport, process, or IP capacity is rejected with an HTTP status, not a close code. See upgrade rejection rules.

Once the socket is open, the item transport reports recoverable errors as error frames and keeps the connection open. It closes the connection in these cases:

  • 1013 (try again later) — the server sheds the connection under pressure. This covers pending-command overflow, which sends a TOO_MANY_PENDING frame first, an outbound queue that fills for a slow consumer, source-event overload on the process, a full authenticated-user bucket during public or handshake authentication, and a full client-IP bucket when a public connection falls back to anonymous access. After authentication, an exceeded per-message rate limit returns a REQUESTS_EXCEEDED error frame and leaves the connection open. During the handshake authentication, the same error closes the connection.
  • 1009 (message too big) — an outbound frame would exceed the 1 MiB frame bound, for example a very large initial snapshot.
  • Authentication close — under handshake and strict, a failed or timed-out authentication closes the connection, and a credential for a different user closes in every mode. See Authentication for the per-mode outcomes.

See Reliability for deployment and recovery guidance. A client that is closed should reconnect, resubscribe, and reread current state.

  • SDK — the JavaScript client for the item protocol.
  • Subscription authorization — permission checks, row-level filters, and delete-feed eligibility.
  • Authentication — credential placement, renewal, and the three authentication modes.
  • Reliability — deployment, limits, and recovery.