Kelly Wire
Copy page
The live socket: how to connect, authenticate and resume, the control messages, and the ticket and single-topic stream operations.
ConnectLink to this section
| Server | URL | What it is |
|---|---|---|
| Kelly Wire | wss://wire.askkelly.ai/v1 | Kelly Wire. Browsers connect from an allowed origin: first-party tickets accept https://askkelly.ai, https://www.askkelly.ai, card tickets the card origins and *.claudemcpcontent.com. A ticket from any other origin closes with 4013. |
| Server-sent events | https://api.askkelly.ai/v1/stream | The SSE alternative on the API host, for widgets and cards whose frames cannot open a socket. |
The protocolLink to this section
Live topics over one WebSocket at wss://wire.askkelly.ai/v1, subprotocol kelly.v1. Every frame is one JSON object. Data messages (snapshot and delta) use one envelope, {op, type, topic, seq, source_ts, emit_ts, source, data}; control messages carry op and their own members. Clients ignore unknown op and type values and unknown members.
Authenticate with a single-use ticket of 60 seconds or less in the first message, within 5 seconds; a browser may offer it as a second subprotocol instead. Long-lived keys never go in a URL. The credential that minted the ticket decides the surface, never the host.
Each topic sends a snapshot with its last_seq, then deltas at last_seq + 1. Subscribing to a topic already held sends its snapshot again. Gap rule: Any sequence jump or checksum mismatch on a topic means: drop that topic's state and fetch a new snapshot, then apply deltas from its last_seq + 1.
The server pings every 20 seconds; two missed pings mean the connection is dead. A resume within 120 seconds sends the session ID, the resume token and the last seq per topic, needs no new ticket and replays missed deltas; topics outside the window get resume_failed and a fresh snapshot. A connection lives 24 hours at most and ends with a drain notice 30 seconds ahead.
Close codes: 4000–4099 Fatal for this request: do not reconnect unchanged.; 4100–4199 Back off: reconnect after at least 1 second, with exponential backoff and jitter.; 4200–4299 Reconnect now and resume immediately..
Every snapshot carries expected_latency (seconds, basis and source) and liveness, and a feed message says when either changes. Nothing claims to be faster than its feed.
Per-book prices reach signed-in first-party tickets only (D4); no edge travels on a shared topic (D21); every member passes the rights register field by field. Off the site, topics carry engine-computed values only.
Tickets and single-topic streamsLink to this section
| Method | Path | Title | Access | Price | MCP tool |
|---|---|---|---|---|---|
| POST | /v1/wire/tickets | Get a Kelly Wire ticket | public | [object Object] | none |
| GET | /v1/stream/{topic} | Stream one topic as server-sent events | public | [object Object] | none |
POST /v1/wire/tickets
Get a Kelly Wire ticketLink to this section
public, no key needed · [object Object] credits, not charged during the beta · status live
Mints a single-use ticket that lives 60 seconds, for the socket (wss://wire.askkelly.ai/v1, sent in the first message) or for one stream (GET /v1/stream/{topic}?ticket=). Needs an API key with the wire:connect scope, or an OAuth token. Stream tickets name their topics; socket tickets may name some (bound to them) or none. Each topic is still checked against the plan when it is opened.
ParametersLink to this section
Body, application/json:
transportstringws for the socket (wss://…/v1), sse for GET /v1/stream/{topic}. A ticket works on the transport it was minted for only.
One of: ws, sse (open: new values may be added).
default "ws"
topicsarray of stringnullableTopics the ticket is bound to, such as game:gm_… or market:mkt_…; required for sse. At most the plan's topic limit. Omit on a socket ticket to subscribe to any topic the plan allows.
max items 5000
modestringnullabletest serves the recorded Sunday (sample data) to any credential; live is refused for a test-mode key. Omitted: the credential's own mode.
One of: live, test (open: new values may be added).
ResponseLink to this section
201 The ticket. It is shown once; responses are never cached.
objectconstrequiredAlways wire_ticket.
Always "wire_ticket".
default "wire_ticket"
ticketstringrequiredThe ticket (wtkt_…); single use, 60 seconds or less.
pattern ^wtkt_[0123456789abcdefghjkmnpqrstvwxyz]{20}$
expires_atstringrequiredWhen the ticket stops working, unused.
format date-time
topicsarray of stringrequirednullableThe topics it is bound to; null for any topic the plan allows.
transportstringrequiredThe transport it works on.
One of: ws, sse (open: new values may be added).
modestringrequiredlive data, or test: the recorded Sunday.
One of: live, test (open: new values may be added).
urlstringrequiredWhere to connect: the socket's URL, or the stream's URL (its {topic} filled in when the ticket names one topic). It never contains the ticket.
ErrorsLink to this section
invalid_request · credential_in_url · unsupported_api_version · unauthenticated · method_not_allowed · rate_limited · internal_error · service_unavailable · quota_exhausted · insufficient_scope · not_entitled · payload_too_large · too_many_topics
ExampleLink to this section
POST https://api.askkelly.ai/v1/wire/tickets
Authorization: Bearer $KELLY_API_KEY
Content-Type: application/json{
"transport": "sse",
"topics": [
"game:gm_hwxy9tewzag7fee8hb01"
],
"mode": "test"
}
{
"type": "https://docs.askkelly.ai/problems/insufficient_scope",
"title": "Insufficient scope",
"status": 403,
"code": "insufficient_scope",
"detail": "This needs the wire:connect scope.",
"param": "wire:connect",
"doc_url": "https://docs.askkelly.ai/problems/insufficient_scope",
"request_id": "req_ecea15c85392623ab2018a24",
"retryable": false,
"display_message": "This app needs one more permission to do that."
}POST https://api.askkelly.ai/v1/wire/tickets
Authorization: Bearer $KELLY_API_KEY
Content-Type: application/json{
"transport": "sse",
"topics": [
"game:gm_hwxy9tewzag7fee8hb01"
],
"mode": "test"
}
{
"object": "wire_ticket",
"ticket": "wtkt_nj7ftwgg0hbac03jg4wb",
"expires_at": "2026-10-04T17:31:00Z",
"topics": [
"game:gm_hwxy9tewzag7fee8hb01"
],
"transport": "sse",
"mode": "test",
"url": "https://api.askkelly.ai/v1/stream/game:gm_hwxy9tewzag7fee8hb01"
}GET /v1/stream/{topic}
Stream one topic as server-sent eventsLink to this section
public, no key needed · [object Object] credits, not charged during the beta · status live
One topic as server-sent events (STR-21), for widgets, simple clients and cards whose host blocks sockets. Each event's id is the topic's seq: first event: snapshot (the topic's state, its last seq, expected latency and liveness), then one event per change, named by its type (game.state, market.update, market.suspended, ...), each seq the previous + 1. Send Last-Event-ID to resume: missed changes replay while they are within the 120-second window, else a fresh snapshot comes first. A gap is answered with a fresh snapshot. A comment line pings every 20 seconds; event: drain comes 30 seconds before the stream's 24-hour limit, and event: error (a catalogue code) when the topic is no longer available, after which the stream ends. Off askkelly.ai streams carry engine-computed values only. Each open stream costs wire_topic_hour credits when it opens and each hour it stays open.
ParametersLink to this section
topicstring · pathrequiredThe topic, such as game:gm_… or market:mkt_… (the Wire topics in the AsyncAPI document).
pattern ^[a-z]+:[A-Za-z0-9_\-/:]+$ · max length 200
ticketstring · queryA ticket minted for sse that names this topic (POST /v1/wire/tickets): single use, 60 seconds or less (STR-24). The one credential a URL may carry; never an API key. Omit it on askkelly.ai, where the session is used.
pattern ^wtkt_[0123456789abcdefghjkmnpqrstvwxyz]{20}$
last_event_idstring · queryThe same as Last-Event-ID, for a new EventSource, which can't set the header; the header wins when both are sent.
pattern ^[0-9]{1,18}$
Last-Event-IDstring · headerThe last event id received (a seq), to resume after a reconnect.
pattern ^[0-9]{1,18}$
ResponseLink to this section
200 The stream. Events: snapshot, one per change (named by its type), drain, error and warning; comment lines are pings.
text/event-stream: Server-sent events; each data line is one Kelly Wire envelope.
ErrorsLink to this section
invalid_request · credential_in_url · unsupported_api_version · unauthenticated · method_not_allowed · rate_limited · internal_error · service_unavailable · quota_exhausted · insufficient_scope · not_entitled · not_found · too_many_topics
Control messagesLink to this section
The socket itself: authentication, subscriptions, heartbeats, drains and resumes. Connect to wss://wire.askkelly.ai/v1 with the subprotocol kelly.v1. Every topic below is subscribed to by name on this one socket.
authLink to this section
You send. The first message: a ticket minted over authenticated REST (POST /v1/wire/tickets with a key or token, POST /me/wire/tickets on askkelly.ai, or get_wire_ticket for a card), within the authentication deadline (STR-6). Browsers may instead offer the ticket as a second subprotocol, kelly.ticket.{ticket}. Sent again mid-session with a new ticket and a request_id, it refreshes the credential in-band (STR-22) and is answered by an ack. Long-lived keys never go in a URL.
Show 3 fields
opconstrequiredAlways auth.
Always "auth".
ticketstringrequiredThe ticket (wtkt_…), single-use, living 60 seconds or less.
request_idstringOnly on an in-band refresh; echoed in the ack.
helloLink to this section
Kelly sends. Sent after authentication or a resume: the heartbeat interval, the session ID and resume token, the resume window, when the connection's lifetime ends and the published limits.
Show 9 fields
opconstrequiredAlways hello.
Always "hello".
session_idstringrequiredHandle for resuming this session (wses_…).
resume_tokenstringrequiredSecret that proves a resume comes from this session; a new one in every hello.
heartbeat_secondsintegerrequiredSeconds between server pings.
min 0
resume_secondsintegerrequiredSeconds of per-topic history kept for a resume.
min 0
expires_atstringrequiredRFC 3339 time the 24-hour lifetime ends; a drain notice comes first.
format date-time
limitsanyrequiredtopics, inbound_per_second and max_frame_bytes for this connection (STR-14).
modestringrequiredlive, or test for test-mode credentials (the recorded Sunday).
One of: live, test (open: new values may be added).
rg_linestringrequiredThe cost line (COMP-1, D19): 21+ and the helpline, from brand.toml [cost_line]. Show it with anything built on a wagering topic; every wagering topic's snapshot carries it too.
subscribeLink to this section
You send. Adds topics. Entitlement is checked per topic (STR-7). Subscribing to a topic already held sends its snapshot again (a resync).
Show 3 fields
opconstrequiredAlways subscribe.
Always "subscribe".
request_idstringEchoed in the ack.
topicsarray of stringrequiredTopics to add.
unsubscribeLink to this section
You send. Removes topics.
Show 3 fields
opconstrequiredAlways unsubscribe.
Always "unsubscribe".
request_idstringEchoed in the ack.
topicsarray of stringrequiredTopics to remove.
list_subscriptionsLink to this section
You send. Asks for the current topic set.
Show 2 fields
opconstrequiredAlways list_subscriptions.
Always "list_subscriptions".
request_idstringEchoed in the ack.
ackLink to this section
Kelly sends. Answers subscribe, unsubscribe, list_subscriptions and an in-band auth, echoing the full current topic set (STR-8).
Show 3 fields
opconstrequiredAlways ack.
Always "ack".
request_idstringThe request this answers.
topicsarray of stringrequiredThe full current topic set.
feedLink to this section
Kelly sends. A topic's expected latency or liveness changed: a poller paused when its budget ran out (STR-5), or no update arrived within the topic's limit, so it is now stale.
Show 4 fields
opconstrequiredAlways feed.
Always "feed".
topicstringrequiredThe topic.
expected_latencyanyseconds, basis and source.
livenessanystate, as_of and limit_seconds.
errorLink to this section
Kelly sends. A per-topic or per-request error with a catalogue code, such as not_entitled (STR-7, STR-22).
Show 6 fields
opconstrequiredAlways error.
Always "error".
codestringrequiredA code from the error catalogue.
detailstringProse; may change.
topicstringThe topic it concerns, if any.
request_idstringThe request it answers.
retryablebooleanWhether retrying can succeed.
warningLink to this section
Kelly sends. The connection is falling behind: read faster or drop topics, or it closes with 4108 (STR-13).
Show 3 fields
opconstrequiredAlways warning.
Always "warning".
codestringrequiredslow_consumer.
detailstringProse; may change.
pingLink to this section
Kelly sends. Heartbeat: answer with pong. Two missed pings mean the connection is dead (STR-12).
Show 2 fields
opconstrequiredAlways ping.
Always "ping".
tsstringrequiredRFC 3339 server time.
pongLink to this section
You send. Answers a ping.
Show 2 fields
opconstrequiredAlways pong.
Always "pong".
tsstringThe ping's ts, echoed.
drainLink to this section
Kelly sends. The server will close this connection: open a new one and resume before the deadline (STR-16, STR-23).
Show 2 fields
opconstrequiredAlways drain.
Always "drain".
deadline_atstringrequiredRFC 3339 time the connection closes.
format date-time
resumeLink to this section
You send. The first message of a reconnect, instead of auth: the session ID, its resume token and the last seq seen per topic (STR-11). Inside the resume window it needs no new ticket.
Show 4 fields
opconstrequiredAlways resume.
Always "resume".
session_idstringrequiredFrom hello.
resume_tokenstringrequiredFrom the latest hello.
last_seqobjectrequiredTopic → last seq seen.
resume_failedLink to this section
Kelly sends. Some or all topics fell outside the replay window: drop their state; a snapshot for each follows (STR-11). When the session itself has expired, the connection then closes with 4011 and the client needs a new ticket.
Show 2 fields
opconstrequiredAlways resume_failed.
Always "resume_failed".
topicsarray of stringrequiredTopics that need a new snapshot.
Generated from Kelly Wire 2026-10-05 and Kelly API 2026-10-05.