Stream protocol reference
Generated from the AsyncAPI document (Status302 Stream0.1.0, AsyncAPI 3.0.0). For how to use the protocol, see the stream guide.
Realtime protocol shared by the Status302 web app and client integrations. One WebSocket connection carries many topic subscriptions. Replayable topics carry a per-topic monotonically increasing seq; clients resume with since after reconnecting. When the requested since is older than the replay window the server answers resync with the REST snapshot to reload from. A topic's seq can start over (the server lost its counters); the server then sends resync (reason gap) on the affected subscriptions and continues from the new seq, so clients forget the last seq they kept for a topic on every resync.
ping is answered with heartbeat echoing its id. Subscribing to a topic again replaces its filter. Close codes are listed in x-close-codes; payloads and filters per topic: https://status302.com/docs/stream/.
Servers
| Server | URL | Authentication |
|---|---|---|
| production | wss://api.status302.com/v1/stream | apiKey or ticket |
| development | ws://localhost:8080/v1/stream | As production |
Authentication
apiKey: Authorization header
Bearer tx_live_<keyId>_<secret> on the upgrade request (server-side clients). The key needs the stream scope and a plan with API access (Pro+; the sales/onsales firehoses need Elite+). Each topic also needs one scope of its REST equivalent, otherwise subscribe is answered forbidden_topic naming the missing scope: event.{id}.kpis and catalog.rows need market:read; sales and event.{id}.sales need sales:read; event.{id}.primary needs maps:read or market:read; onsales needs catalog:read; alerts needs alerts:write. Each connection holds one of the org's stream connection slots (plan quota; refused with 4429) and at most the plan's subscriptions per connection. Revoking the key, or the end of its rotation grace or expiry, closes its connections with 4401; changing its scopes closes them with 4409.
ticket: ticket query parameter
Single-use ticket (60 s) from POST /v1/stream/ticket, for browsers; issued to a session or to an API key with the stream scope (the connection then acts as that key, topic scopes included). A user holds at most 20 open connections and a session at most 10 (across every server; 4429 beyond); API keys count against their org's quota instead.
Client messages
JSON text frames you send. Each request carries an id that the reply echoes.
subscribe
{"op":"subscribe","id":"r1","topic":"event.4102.sales"}| Field | Type | Required | Description |
|---|---|---|---|
op | "subscribe" | yes | |
id | string | yes | Client request id echoed in the reply. |
topic | Topic | yes | |
since | integer | no | Replay everything after this seq (replayable topics only). |
filter | object | no | Topic-specific filter (per topic: https://status302.com/docs/stream/). |
unsubscribe
{"op":"unsubscribe","id":"r1","topic":"event.4102.sales"}| Field | Type | Required | Description |
|---|---|---|---|
op | "unsubscribe" | yes | |
id | string | yes | |
topic | Topic | yes |
ping
{"op":"ping","id":"r1"}| Field | Type | Required | Description |
|---|---|---|---|
op | "ping" | yes | |
id | string | yes |
Server messages
JSON text frames the server sends, distinguished by type.
subscribed
{"type":"subscribed","id":"r1","topic":"event.4102.sales","seq":48231}| Field | Type | Required | Description |
|---|---|---|---|
type | "subscribed" | yes | |
id | string | yes | |
topic | Topic | yes | |
seq | integer | yes | Current head seq of the topic. |
snapshot | string | no | REST path returning the full current state. |
data
{"type":"data","topic":"event.4102.sales","seq":48231,"ts":"2026-10-28T18:04:11.204Z","data":{}}| Field | Type | Required | Description |
|---|---|---|---|
type | "data" | yes | |
topic | Topic | yes | |
seq | integer | yes | |
ts | string, date-time | yes | |
data | object | yes |
resync
{"type":"resync","topic":"event.4102.sales","reason":"gap","snapshot":"<snapshot>"}| Field | Type | Required | Description |
|---|---|---|---|
type | "resync" | yes | |
topic | Topic | yes | |
reason | "gap" | "backpressure" | yes | |
snapshot | string | yes |
error
{"type":"error","code":"bad_request","message":"<message>"}| Field | Type | Required | Description |
|---|---|---|---|
type | "error" | yes | |
id | string | no | |
code | "bad_request" | "unknown_topic" | "forbidden_topic" | "subscription_limit" | "rate_limited" | "unavailable" | yes | unknown_topic: not a topic, or an event topic whose event id does not exist. forbidden_topic: the plan, or the API key's scopes, do not allow the topic. unavailable: the topic could not be read right now (Redis); retry the subscribe. |
message | string | yes |
heartbeat
{"type":"heartbeat","ts":"2026-10-28T18:04:11.204Z"}| Field | Type | Required | Description |
|---|---|---|---|
type | "heartbeat" | yes | |
id | string | no | The ping this answers. |
ts | string, date-time | yes |
Error codes
The code of an error message. The connection stays open; the request it answers had no effect.
bad_requestunknown_topicforbidden_topicsubscription_limitrate_limitedunavailable
unknown_topic: not a topic, or an event topic whose event id does not exist. forbidden_topic: the plan, or the API key's scopes, do not allow the topic. unavailable: the topic could not be read right now (Redis); retry the subscribe.
Topics
One of: event.{id}.kpis, event.{id}.sales, event.{id}.primary, sales, onsales, alerts, catalog.rows (app only). Reserved, without a producer yet (answered unknown_topic): event.{id}.listings, event.{id}.map, catalog.movers.
Valid topic names match ^(event\.[1-9][0-9]*\.(kpis|sales|primary)|sales|onsales|catalog\.rows|alerts)$.
Close codes
| Code | Name | Meaning | Reconnect? |
|---|---|---|---|
1001 | going_away | Idle timeout (no client message or pong for 60 s; the server sends heartbeat and pings every 20 s), or a failed write. | Yes, with backoff; resume with since |
1009 | message_too_big | A client message exceeded 64 KiB. | Yes, with backoff; resume with since |
1012 | service_restart | The server is restarting; reconnect and resume with since. | Yes, with backoff; resume with since |
4008 | slow_consumer | The connection's send buffer overflowed; reconnect and resume with since. | Yes, with backoff; resume with since |
4401 | unauthorized | No credentials, ticket invalid or already used, session or API key revoked, key expired or past its rotation grace. Get new credentials before reconnecting. | No, not with the same credentials |
4403 | forbidden | The plan does not allow streaming, or the API key lacks the stream scope. | No, not with the same credentials |
4409 | reconnect | Role, active org, plan or API key scopes changed; fetch a new ticket if you use one, reconnect and resume with since. | Yes, with backoff; resume with since |
4429 | too_many_connections | The org's stream connection quota is used up (API keys), or the user or session already holds its maximum of open connections (signed-in clients; 20 per user and 10 per session by default); close another connection or retry later. | Yes, with backoff; resume with since |

