Skip to Content
SkewtapeDocs

Live feed (WebSocket)

GET
/v1/feed

WebSocket upgrade (ADR-0013, ARCHITECTURE 10). Builder and Pro plans; the plans table says how many connections an account may hold (Builder 3, Pro 10, counted across all its keys). Not served by a generated handler: this operation documents the protocol.

Authentication: the X-API-Key header on the upgrade, or the first message {"op":"auth","key":"st_live_..."} within 10 seconds.

Messages from the client: {"op":"subscribe","topics":["markets","resolutions","trades","event_spread"], "venues":["kalshi"],"markets":[{"venue":"polymarket","market_id":"0x..."}], "events":["st_evt_00042"],"resume_from":123} replaces the subscription (topics required; the filters are ORed and optional; the events filter, matched event ids, narrows event_spread, whose rows belong to an event and not a venue, so the venue and market filters do not narrow them; it needs that topic: with other topics alone it is refused, and beside event_spread it leaves those topics to the venue and market filters, whole when there are none; a market filter cuts a trades_batch to its markets' fills); {"op":"ping"} answers {"op":"pong"} at once, even during a replay. markets, resolutions and trades need Builder or above, event_spread Pro; a plan that loses a topic during a connection is told (error topic_not_in_plan) and keeps its other topics.

Trades (E15-T3) are fills from venue streams, one event per fill with the stored row's identity (venue, tx_hash, log_index) up to 400 a second per venue, the rest of the second folded into one trades_batch; the hourly walk stores the row afterwards, and a leader that takes over re-reads the last blocks, so dedupe by identity. event_spread is one approved matched event's fee-adjusted spread, at most one event per matched event per five seconds (FeedSpread). Right after subscribed, a subscription to event_spread receives the latest spread of each event it names as snapshot messages (op snapshot, FeedSnapshot): a state with no seq, never a point to resume from; the replay then carries the other topics and live spreads follow as events, so a replay returns the latest per event, never each tick. A subscription sent before the instance has read the log once is answered error not_ready: send it again. Subscribes sent while the connection is busy (a replay, a slow write) are not queued: the latest one is applied when the connection is free, and each earlier one that never ran is answered error superseded.

Messages from the server, each with op: hello (connection_id, plan, the current seq, the replay window and the reconnect schedule), subscribed (what was accepted, replay_to, masked_venues), snapshot (one FeedSnapshot: the latest spread of a matched event, no seq), event (one FeedEvent: seq, topic, kind, venue, market_id, at, payload), resync (resume_from is older than the replay window: read the API for what you missed, live events follow), reconnect (sent at 55 minutes with resume_from; the connection closes at 59 minutes, before the 60-minute platform limit), error (code, message).

Replay (D2): resume_from replays the stored events after that seq that match the subscription, then switches to live with no gap and no duplicate; a client keeps the last seq it saw. Every connection also receives the feed's own gap notices (topic feed, kind gap) when a venue stream reconnected: the stored data has no gap, so read the API for that window or wait for the next hourly run.

Policy (ADR-0028): a subscription naming a venue the live_data policy blocks is refused (error venue_blocked); a masked venue's events ship with the listed fields null and blocked true.

Close codes: 4000 reconnect (the age limit, or a registration that expired while the server could not heartbeat; both carry resume_from), 4001 unauthorized (also when the key is revoked during the connection: keys and plans are read again every few minutes), 4003 plan (no feed, or a topic the plan no longer covers), 4008 connection limit, 4009 slow consumer (the reason carries resume_from), 4029 too many failed key lookups, 4400 bad message, 1001 server restarting.

Header Parameters

X-API-Key?string

The API key; or send the auth message first.

Response Body

Switching Protocols. The WebSocket carries the messages described above.

Example Request
curl -X GET 'https://api.skewtape.com/v1/feed'
Example Response
Empty

Powered by Kalshi Research