{
  "asyncapi": "3.1.0",
  "info": {
    "title": "Kelly Wire",
    "version": "2026-10-05",
    "description": "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.\n\nAuthenticate 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.\n\nEach 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.\n\nThe 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.\n\nClose 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..\n\nEvery 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.\n\nPer-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."
  },
  "defaultContentType": "application/json",
  "servers": {
    "production": {
      "host": "wire.askkelly.ai",
      "pathname": "/v1",
      "protocol": "wss",
      "protocolVersion": "13",
      "title": "Kelly Wire",
      "description": "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.",
      "security": [
        {
          "$ref": "#/components/securitySchemes/ticket"
        }
      ],
      "bindings": {
        "ws": {
          "bindingVersion": "0.1.0"
        }
      }
    },
    "sse": {
      "host": "api.askkelly.ai",
      "pathname": "/v1/stream",
      "protocol": "https",
      "title": "Server-sent events",
      "description": "The SSE alternative on the API host, for widgets and cards whose frames cannot open a socket."
    }
  },
  "channels": {
    "connection": {
      "address": "/",
      "title": "Connection",
      "description": "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.",
      "servers": [
        {
          "$ref": "#/servers/production"
        }
      ],
      "messages": {
        "auth": {
          "$ref": "#/components/messages/auth"
        },
        "hello": {
          "$ref": "#/components/messages/hello"
        },
        "subscribe": {
          "$ref": "#/components/messages/subscribe"
        },
        "unsubscribe": {
          "$ref": "#/components/messages/unsubscribe"
        },
        "list_subscriptions": {
          "$ref": "#/components/messages/list_subscriptions"
        },
        "ack": {
          "$ref": "#/components/messages/ack"
        },
        "feed": {
          "$ref": "#/components/messages/feed"
        },
        "error": {
          "$ref": "#/components/messages/error"
        },
        "warning": {
          "$ref": "#/components/messages/warning"
        },
        "ping": {
          "$ref": "#/components/messages/ping"
        },
        "pong": {
          "$ref": "#/components/messages/pong"
        },
        "drain": {
          "$ref": "#/components/messages/drain"
        },
        "resume": {
          "$ref": "#/components/messages/resume"
        },
        "resume_failed": {
          "$ref": "#/components/messages/resume_failed"
        }
      },
      "bindings": {
        "ws": {
          "method": "GET",
          "query": {
            "type": "object",
            "properties": {
              "debug_reconnects": {
                "type": "integer",
                "enum": [
                  1
                ],
                "description": "Test mode only: the connection is drained every 30 seconds, so a client can exercise its resume path (STR-19)."
              }
            }
          },
          "headers": {
            "type": "object",
            "properties": {
              "Sec-WebSocket-Protocol": {
                "type": "string",
                "pattern": "^kelly\\.v1(, ?kelly\\.ticket\\.wtkt_[A-Za-z0-9_-]+)?$",
                "description": "Offer the subprotocol kelly.v1 (STR-2); another version closes with 4012. A browser may offer the ticket as a second subprotocol, kelly.ticket.{ticket}, which Wire never echoes (STR-6)."
              }
            }
          },
          "bindingVersion": "0.1.0"
        }
      },
      "x-status": "live"
    },
    "game": {
      "address": "game:{id}",
      "title": "Game topic",
      "description": "One game's live state: score, clock, possession and Kelly's template-written last play (D16). A final score that changes afterwards arrives as game.corrected, so plans holding the game revalidate (strategy-playbooks.md S15). Kind: state (coalesces to the latest value per key). Entitlement: Anonymous and every plan.",
      "servers": [
        {
          "$ref": "#/servers/production"
        }
      ],
      "parameters": {
        "id": {
          "description": "A game ID (gm_…).",
          "examples": [
            "gm_m36rrk9v9wrm650n78nf"
          ]
        }
      },
      "messages": {
        "game_snapshot": {
          "$ref": "#/components/messages/game_snapshot"
        },
        "game_delta": {
          "$ref": "#/components/messages/game_delta"
        }
      },
      "x-status": "live"
    },
    "market": {
      "address": "market:{market_id}",
      "title": "Market topic",
      "description": "One market: its per-book prices (first-party tickets only, D4) and, from K3, the consensus line, no-vig probabilities, break-evens, hold and movement. It replaces K1's market:{game_id}:{market} (K6). Kind: state (coalesces to the latest value per key). Entitlement: Anonymous and every plan; props on API connections are Pro (D24); props in cards and on askkelly.ai for every plan.",
      "servers": [
        {
          "$ref": "#/servers/production"
        }
      ],
      "parameters": {
        "market_id": {
          "description": "A market ID (mkt_…): any market, futures and props included.",
          "examples": [
            "mkt_ask1ghxe1wvgq7ryppvh"
          ]
        }
      },
      "messages": {
        "market_snapshot": {
          "$ref": "#/components/messages/market_snapshot"
        },
        "market_delta": {
          "$ref": "#/components/messages/market_delta"
        }
      },
      "x-status": "live"
    },
    "player": {
      "address": "player:{id}",
      "title": "Player topic",
      "description": "One player's projection, range, injury status and usage as they change. Kind: state (coalesces to the latest value per key). Entitlement: Anonymous and every plan.",
      "servers": [
        {
          "$ref": "#/servers/production"
        }
      ],
      "parameters": {
        "id": {
          "description": "A player ID (plr_…).",
          "examples": [
            "plr_jnz83jymsbrckkvynjfn"
          ]
        }
      },
      "messages": {
        "player_snapshot": {
          "$ref": "#/components/messages/player_snapshot"
        },
        "player_delta": {
          "$ref": "#/components/messages/player_delta"
        }
      },
      "x-status": "planned",
      "x-planned": {
        "decisions": [
          "P8",
          "D24"
        ],
        "date": "2026-10-16",
        "session": "K10"
      }
    },
    "board": {
      "address": "board:{id}",
      "title": "Board topic",
      "description": "One board's ordering and values as they change: pools, rankings and draft boards (K6; publishers arrive with the boards). Kind: state (coalesces to the latest value per key). Entitlement: Anonymous and every plan.",
      "servers": [
        {
          "$ref": "#/servers/production"
        }
      ],
      "parameters": {
        "id": {
          "description": "A board ID (brd_…): a pool, a ranking or a draft board.",
          "examples": [
            "brd_4cjp3ce537drr73gynx9"
          ]
        }
      },
      "messages": {
        "board_snapshot": {
          "$ref": "#/components/messages/board_snapshot"
        },
        "board_delta": {
          "$ref": "#/components/messages/board_delta"
        }
      },
      "x-status": "planned",
      "x-planned": {
        "decisions": [
          "P8",
          "D24"
        ],
        "date": "2026-10-16",
        "session": "K10"
      }
    },
    "reference": {
      "address": "reference:{topic_id}",
      "title": "Reference topic",
      "description": "Revisions to one reference entry: a new published version replaces the old (K6; K7's review publishes). Kind: state (coalesces to the latest value per key). Entitlement: Anonymous and every plan.",
      "servers": [
        {
          "$ref": "#/servers/production"
        }
      ],
      "parameters": {
        "topic_id": {
          "description": "A reference topic ID, such as law/us-ny (GET /v1/reference/{topic_id})."
        }
      },
      "messages": {
        "reference_snapshot": {
          "$ref": "#/components/messages/reference_snapshot"
        },
        "reference_delta": {
          "$ref": "#/components/messages/reference_delta"
        }
      },
      "x-status": "planned",
      "x-planned": {
        "decisions": [
          "P8",
          "ADR-K016"
        ],
        "date": "2026-10-16",
        "session": "K10"
      }
    },
    "league": {
      "address": "league:{id}",
      "title": "League topic",
      "description": "The owner's league: live scores, standings and matchup win probabilities. Pro, bound to its owner (published from K10's league resource). Kind: state (coalesces to the latest value per key). Entitlement: Signed-in owner on Pro.",
      "servers": [
        {
          "$ref": "#/servers/production"
        }
      ],
      "parameters": {
        "id": {
          "description": "A league ID (lg_…) the signed-in person owns.",
          "examples": [
            "lg_74wcg2rs9wmbdwq8ac3z"
          ]
        }
      },
      "messages": {
        "league_snapshot": {
          "$ref": "#/components/messages/league_snapshot"
        },
        "league_delta": {
          "$ref": "#/components/messages/league_delta"
        }
      },
      "x-status": "planned",
      "x-planned": {
        "decisions": [
          "P8",
          "K10-A4"
        ],
        "date": "2026-10-16",
        "session": "K10"
      }
    },
    "answer": {
      "address": "answer:{id}",
      "title": "Answer topic",
      "description": "One streamed answer: sentences as they are written, citations, then live figure updates while anyone watches (D22). It stays open after answer.completed while anything subscribes. Sentences are never retracted. Kind: event (never coalesces). Entitlement: The answer's own subject; public answers to anyone holding the ID.",
      "servers": [
        {
          "$ref": "#/servers/production"
        }
      ],
      "parameters": {
        "id": {
          "description": "An answer ID (ans_…).",
          "examples": [
            "ans_v125jaqk8p4mfn45x8sh"
          ]
        }
      },
      "messages": {
        "answer_snapshot": {
          "$ref": "#/components/messages/answer_snapshot"
        },
        "answer_delta": {
          "$ref": "#/components/messages/answer_delta"
        }
      },
      "x-status": "planned",
      "x-planned": {
        "decisions": [
          "P8",
          "D22"
        ],
        "date": "2026-10-16",
        "session": "K8"
      }
    },
    "stream": {
      "address": "/{topic}",
      "title": "Server-sent events",
      "description": "The SSE alternative: GET https://api.askkelly.ai/v1/stream/{topic} streams one topic's snapshot, then its deltas, through the same hub, snapshots and rights gate as the socket (STR-1, STR-21). Each event's id is the delta's seq, its event is the type, and its data is the message as JSON. A dropped stream resumes with Last-Event-ID set to the last seq, inside the 120-second window; outside it a fresh snapshot arrives. It authenticates with a first-party cookie plus an Origin check, or with a single-use ticket of 60 seconds or less in the query string, bound to the topic (STR-24). A socket ticket does not open SSE, and an SSE ticket does not open a socket. The same per-IP limits apply.",
      "servers": [
        {
          "$ref": "#/servers/sse"
        }
      ],
      "parameters": {
        "topic": {
          "description": "One topic address from the channels above, such as a game or market topic."
        }
      },
      "messages": {
        "stream_snapshot": {
          "$ref": "#/components/messages/stream_snapshot"
        },
        "stream_delta": {
          "$ref": "#/components/messages/stream_delta"
        }
      },
      "x-status": "live"
    }
  },
  "operations": {
    "receive_auth": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "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.",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/auth"
        }
      ],
      "reply": {
        "channel": {
          "$ref": "#/channels/connection"
        },
        "messages": [
          {
            "$ref": "#/channels/connection/messages/hello"
          },
          {
            "$ref": "#/channels/connection/messages/ack"
          }
        ]
      }
    },
    "send_hello": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "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.",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/hello"
        }
      ]
    },
    "receive_subscribe": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "Adds topics. Entitlement is checked per topic (STR-7). Subscribing to a topic already held sends its snapshot again (a resync).",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/subscribe"
        }
      ],
      "reply": {
        "channel": {
          "$ref": "#/channels/connection"
        },
        "messages": [
          {
            "$ref": "#/channels/connection/messages/ack"
          }
        ]
      }
    },
    "receive_unsubscribe": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "Removes topics.",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/unsubscribe"
        }
      ],
      "reply": {
        "channel": {
          "$ref": "#/channels/connection"
        },
        "messages": [
          {
            "$ref": "#/channels/connection/messages/ack"
          }
        ]
      }
    },
    "receive_list_subscriptions": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "Asks for the current topic set.",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/list_subscriptions"
        }
      ],
      "reply": {
        "channel": {
          "$ref": "#/channels/connection"
        },
        "messages": [
          {
            "$ref": "#/channels/connection/messages/ack"
          }
        ]
      }
    },
    "send_ack": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "Answers subscribe, unsubscribe, list_subscriptions and an in-band auth, echoing the full current topic set (STR-8).",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/ack"
        }
      ]
    },
    "send_feed": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "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.",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/feed"
        }
      ]
    },
    "send_error": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "A per-topic or per-request error with a catalogue code, such as not_entitled (STR-7, STR-22).",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/error"
        }
      ]
    },
    "send_warning": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "The connection is falling behind: read faster or drop topics, or it closes with 4108 (STR-13).",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/warning"
        }
      ]
    },
    "send_ping": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "Heartbeat: answer with pong. Two missed pings mean the connection is dead (STR-12).",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/ping"
        }
      ]
    },
    "receive_pong": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "Answers a ping.",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/pong"
        }
      ]
    },
    "send_drain": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "The server will close this connection: open a new one and resume before the deadline (STR-16, STR-23).",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/drain"
        }
      ]
    },
    "receive_resume": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "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.",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/resume"
        }
      ],
      "reply": {
        "channel": {
          "$ref": "#/channels/connection"
        },
        "messages": [
          {
            "$ref": "#/channels/connection/messages/hello"
          },
          {
            "$ref": "#/channels/connection/messages/resume_failed"
          }
        ]
      }
    },
    "send_resume_failed": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/connection"
      },
      "summary": "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.",
      "messages": [
        {
          "$ref": "#/channels/connection/messages/resume_failed"
        }
      ]
    },
    "send_game": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/game"
      },
      "summary": "Kelly sends the game topic's snapshot, then its deltas.",
      "messages": [
        {
          "$ref": "#/channels/game/messages/game_snapshot"
        },
        {
          "$ref": "#/channels/game/messages/game_delta"
        }
      ]
    },
    "send_market": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/market"
      },
      "summary": "Kelly sends the market topic's snapshot, then its deltas.",
      "messages": [
        {
          "$ref": "#/channels/market/messages/market_snapshot"
        },
        {
          "$ref": "#/channels/market/messages/market_delta"
        }
      ]
    },
    "send_player": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/player"
      },
      "summary": "Kelly sends the player topic's snapshot, then its deltas.",
      "messages": [
        {
          "$ref": "#/channels/player/messages/player_snapshot"
        },
        {
          "$ref": "#/channels/player/messages/player_delta"
        }
      ]
    },
    "send_board": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/board"
      },
      "summary": "Kelly sends the board topic's snapshot, then its deltas.",
      "messages": [
        {
          "$ref": "#/channels/board/messages/board_snapshot"
        },
        {
          "$ref": "#/channels/board/messages/board_delta"
        }
      ]
    },
    "send_reference": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/reference"
      },
      "summary": "Kelly sends the reference topic's snapshot, then its deltas.",
      "messages": [
        {
          "$ref": "#/channels/reference/messages/reference_snapshot"
        },
        {
          "$ref": "#/channels/reference/messages/reference_delta"
        }
      ]
    },
    "send_league": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/league"
      },
      "summary": "Kelly sends the league topic's snapshot, then its deltas.",
      "messages": [
        {
          "$ref": "#/channels/league/messages/league_snapshot"
        },
        {
          "$ref": "#/channels/league/messages/league_delta"
        }
      ]
    },
    "send_answer": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/answer"
      },
      "summary": "Kelly sends the answer topic's snapshot, then its deltas.",
      "messages": [
        {
          "$ref": "#/channels/answer/messages/answer_snapshot"
        },
        {
          "$ref": "#/channels/answer/messages/answer_delta"
        }
      ]
    },
    "send_stream": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/stream"
      },
      "summary": "Kelly sends one topic's snapshot, then its deltas, as server-sent events.",
      "messages": [
        {
          "$ref": "#/channels/stream/messages/stream_snapshot"
        },
        {
          "$ref": "#/channels/stream/messages/stream_delta"
        }
      ],
      "bindings": {
        "http": {
          "method": "GET",
          "query": {
            "type": "object",
            "properties": {
              "ticket": {
                "type": "string",
                "description": "A single-use SSE ticket (wtkt_…) bound to this topic, living 60 seconds or less. Absent when a first-party cookie authenticates the request."
              }
            }
          },
          "bindingVersion": "0.3.0"
        }
      }
    }
  },
  "components": {
    "schemas": {
      "WireEnvelope": {
        "type": "object",
        "x-kelly-object": "wire_message",
        "description": "One Kelly Wire message (STR-3). `op` is the protocol operation, `type` the event type (`game.state`, `answer.delta`). Clients ignore unknown `op` and `type` values.",
        "required": [
          "op",
          "emit_ts"
        ],
        "properties": {
          "op": {
            "type": "string",
            "minLength": 1,
            "description": "Protocol operation, such as snapshot or delta; ignore unknown values."
          },
          "type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Event type, such as game.state or answer.delta."
          },
          "topic": {
            "type": [
              "string",
              "null"
            ],
            "description": "The topic, such as game:{id}; absent on connection messages."
          },
          "seq": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Per-topic sequence: each delta is the last seq + 1."
          },
          "source_ts": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the fact happened upstream."
          },
          "emit_ts": {
            "type": "string",
            "format": "date-time",
            "description": "When the message left Kelly."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who produced the data."
          },
          "data": {
            "type": "object",
            "description": "The typed payload for this op and type."
          }
        },
        "additionalProperties": false
      },
      "ExpectedLatency": {
        "type": "object",
        "x-kelly-object": "wire_expected_latency",
        "required": [
          "seconds",
          "basis"
        ],
        "description": "STR-5: how far behind the real world a topic runs, and why. Nothing claims to be faster than its feed.",
        "properties": {
          "seconds": {
            "type": "integer",
            "minimum": 0,
            "description": "Typical seconds behind the source."
          },
          "basis": {
            "enum": [
              "watched_poll",
              "scheduled",
              "paused",
              "computed",
              "test_stream",
              "none"
            ],
            "description": "watched_poll: A game someone watches: the odds poller polls it every 30 seconds (D23). scheduled: A batch feed with a published schedule, such as a nightly or hourly sweep. paused: The feed is paused because the month's credits are spent; the figure holds its last value. computed: Computed by Kelly from other topics, so it follows their feeds. test_stream: The recorded Sunday played on a loop (test mode). none: No live feed sits behind this topic."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "The feed behind it, if any."
          }
        }
      },
      "Liveness": {
        "type": "object",
        "x-kelly-object": "wire_liveness",
        "required": [
          "state"
        ],
        "description": "Whether a live step may rely on a topic now: sent in every snapshot and, when the state flips, in a feed message.",
        "properties": {
          "state": {
            "enum": [
              "live",
              "stale",
              "not_tracked"
            ],
            "description": "live: Updates are arriving within the topic's limit; a live step may rely on it. stale: No update arrived within the topic's limit (expected latency plus stale_seconds). not_tracked: The topic has no live feed (basis scheduled, paused or none); there is nothing to go stale."
          },
          "as_of": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The newest source time Wire has seen on the topic."
          },
          "limit_seconds": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Seconds after as_of at which the topic counts as stale: its expected latency plus stale_seconds."
          }
        }
      },
      "WireLimits": {
        "type": "object",
        "x-kelly-object": "wire_limits",
        "required": [
          "topics",
          "inbound_per_second",
          "max_frame_bytes"
        ],
        "description": "The limits published in hello (STR-14).",
        "properties": {
          "topics": {
            "type": "integer",
            "minimum": 0,
            "description": "Topics this connection may hold."
          },
          "inbound_per_second": {
            "type": "integer",
            "minimum": 1,
            "description": "Client messages per second."
          },
          "max_frame_bytes": {
            "type": "integer",
            "minimum": 1,
            "description": "The largest frame accepted."
          }
        }
      }
    },
    "messages": {
      "auth": {
        "name": "auth",
        "title": "Auth",
        "summary": "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.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op",
            "ticket"
          ],
          "properties": {
            "op": {
              "const": "auth",
              "description": "Always `auth`."
            },
            "ticket": {
              "type": "string",
              "description": "The ticket (wtkt_…), single-use, living 60 seconds or less."
            },
            "request_id": {
              "type": "string",
              "description": "Only on an in-band refresh; echoed in the ack."
            }
          }
        }
      },
      "hello": {
        "name": "hello",
        "title": "Hello",
        "summary": "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.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op",
            "session_id",
            "resume_token",
            "heartbeat_seconds",
            "resume_seconds",
            "expires_at",
            "limits",
            "mode",
            "rg_line"
          ],
          "properties": {
            "op": {
              "const": "hello",
              "description": "Always `hello`."
            },
            "session_id": {
              "type": "string",
              "description": "Handle for resuming this session (wses_…)."
            },
            "resume_token": {
              "type": "string",
              "description": "Secret that proves a resume comes from this session; a new one in every hello."
            },
            "heartbeat_seconds": {
              "type": "integer",
              "minimum": 0,
              "description": "Seconds between server pings."
            },
            "resume_seconds": {
              "type": "integer",
              "minimum": 0,
              "description": "Seconds of per-topic history kept for a resume."
            },
            "expires_at": {
              "type": "string",
              "format": "date-time",
              "description": "RFC 3339 time the 24-hour lifetime ends; a drain notice comes first."
            },
            "limits": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/WireLimits"
                }
              ],
              "description": "topics, inbound_per_second and max_frame_bytes for this connection (STR-14)."
            },
            "mode": {
              "type": "string",
              "enum": [
                "live",
                "test"
              ],
              "description": "live, or test for test-mode credentials (the recorded Sunday)."
            },
            "rg_line": {
              "type": "string",
              "description": "The 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."
            }
          }
        }
      },
      "subscribe": {
        "name": "subscribe",
        "title": "Subscribe",
        "summary": "Adds topics. Entitlement is checked per topic (STR-7). Subscribing to a topic already held sends its snapshot again (a resync).",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op",
            "topics"
          ],
          "properties": {
            "op": {
              "const": "subscribe",
              "description": "Always `subscribe`."
            },
            "request_id": {
              "type": "string",
              "description": "Echoed in the ack."
            },
            "topics": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "Topics to add."
            }
          }
        }
      },
      "unsubscribe": {
        "name": "unsubscribe",
        "title": "Unsubscribe",
        "summary": "Removes topics.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op",
            "topics"
          ],
          "properties": {
            "op": {
              "const": "unsubscribe",
              "description": "Always `unsubscribe`."
            },
            "request_id": {
              "type": "string",
              "description": "Echoed in the ack."
            },
            "topics": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "Topics to remove."
            }
          }
        }
      },
      "list_subscriptions": {
        "name": "list_subscriptions",
        "title": "List subscriptions",
        "summary": "Asks for the current topic set.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op"
          ],
          "properties": {
            "op": {
              "const": "list_subscriptions",
              "description": "Always `list_subscriptions`."
            },
            "request_id": {
              "type": "string",
              "description": "Echoed in the ack."
            }
          }
        }
      },
      "ack": {
        "name": "ack",
        "title": "Ack",
        "summary": "Answers subscribe, unsubscribe, list_subscriptions and an in-band auth, echoing the full current topic set (STR-8).",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op",
            "topics"
          ],
          "properties": {
            "op": {
              "const": "ack",
              "description": "Always `ack`."
            },
            "request_id": {
              "type": "string",
              "description": "The request this answers."
            },
            "topics": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "The full current topic set."
            }
          }
        }
      },
      "feed": {
        "name": "feed",
        "title": "Feed",
        "summary": "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.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op",
            "topic"
          ],
          "properties": {
            "op": {
              "const": "feed",
              "description": "Always `feed`."
            },
            "topic": {
              "type": "string",
              "description": "The topic."
            },
            "expected_latency": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ExpectedLatency"
                }
              ],
              "description": "seconds, basis and source."
            },
            "liveness": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Liveness"
                }
              ],
              "description": "state, as_of and limit_seconds."
            }
          }
        }
      },
      "error": {
        "name": "error",
        "title": "Error",
        "summary": "A per-topic or per-request error with a catalogue code, such as not_entitled (STR-7, STR-22).",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op",
            "code"
          ],
          "properties": {
            "op": {
              "const": "error",
              "description": "Always `error`."
            },
            "code": {
              "type": "string",
              "description": "A code from the error catalogue."
            },
            "detail": {
              "type": "string",
              "description": "Prose; may change."
            },
            "topic": {
              "type": "string",
              "description": "The topic it concerns, if any."
            },
            "request_id": {
              "type": "string",
              "description": "The request it answers."
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying can succeed."
            }
          }
        }
      },
      "warning": {
        "name": "warning",
        "title": "Warning",
        "summary": "The connection is falling behind: read faster or drop topics, or it closes with 4108 (STR-13).",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op",
            "code"
          ],
          "properties": {
            "op": {
              "const": "warning",
              "description": "Always `warning`."
            },
            "code": {
              "type": "string",
              "description": "slow_consumer."
            },
            "detail": {
              "type": "string",
              "description": "Prose; may change."
            }
          }
        }
      },
      "ping": {
        "name": "ping",
        "title": "Ping",
        "summary": "Heartbeat: answer with pong. Two missed pings mean the connection is dead (STR-12).",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op",
            "ts"
          ],
          "properties": {
            "op": {
              "const": "ping",
              "description": "Always `ping`."
            },
            "ts": {
              "type": "string",
              "description": "RFC 3339 server time."
            }
          }
        }
      },
      "pong": {
        "name": "pong",
        "title": "Pong",
        "summary": "Answers a ping.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op"
          ],
          "properties": {
            "op": {
              "const": "pong",
              "description": "Always `pong`."
            },
            "ts": {
              "type": "string",
              "description": "The ping's ts, echoed."
            }
          }
        }
      },
      "drain": {
        "name": "drain",
        "title": "Drain",
        "summary": "The server will close this connection: open a new one and resume before the deadline (STR-16, STR-23).",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op",
            "deadline_at"
          ],
          "properties": {
            "op": {
              "const": "drain",
              "description": "Always `drain`."
            },
            "deadline_at": {
              "type": "string",
              "format": "date-time",
              "description": "RFC 3339 time the connection closes."
            }
          }
        }
      },
      "resume": {
        "name": "resume",
        "title": "Resume",
        "summary": "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.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op",
            "session_id",
            "resume_token",
            "last_seq"
          ],
          "properties": {
            "op": {
              "const": "resume",
              "description": "Always `resume`."
            },
            "session_id": {
              "type": "string",
              "description": "From hello."
            },
            "resume_token": {
              "type": "string",
              "description": "From the latest hello."
            },
            "last_seq": {
              "type": "object",
              "additionalProperties": {
                "type": "integer",
                "minimum": 0
              },
              "description": "Topic → last seq seen."
            }
          }
        }
      },
      "resume_failed": {
        "name": "resume_failed",
        "title": "Resume failed",
        "summary": "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.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_control",
          "required": [
            "op",
            "topics"
          ],
          "properties": {
            "op": {
              "const": "resume_failed",
              "description": "Always `resume_failed`."
            },
            "topics": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "Topics that need a new snapshot."
            }
          }
        }
      },
      "game_snapshot": {
        "name": "game_snapshot",
        "title": "Game snapshot",
        "summary": "The game topic's full state with its last_seq, expected latency and liveness.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "snapshot",
              "description": "Always `snapshot`."
            },
            "type": {
              "const": "game.snapshot",
              "description": "Always `game.snapshot`."
            },
            "topic": {
              "type": "string",
              "pattern": "^game:",
              "description": "A game:{id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 0,
              "description": "The topic's last_seq (STR-9): the next delta is last_seq + 1. A topic with no events yet has seq 0 and data null."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Wire sent the snapshot (Wire's own time)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": [
                "object",
                "null"
              ],
              "description": "The topic's latest state, every member passed through the rights gate for this connection's surface; null for a topic with no events yet. Clients ignore unknown members."
            },
            "expected_latency": {
              "$ref": "#/components/schemas/ExpectedLatency"
            },
            "liveness": {
              "$ref": "#/components/schemas/Liveness"
            }
          }
        }
      },
      "game_delta": {
        "name": "game_delta",
        "title": "Game delta",
        "summary": "The game topic's one change; seq is the previous seq + 1.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "delta",
              "description": "Always `delta`."
            },
            "type": {
              "enum": [
                "game.state",
                "game.final",
                "game.corrected"
              ],
              "description": "Event types this topic carries; ignore unknown values."
            },
            "topic": {
              "type": "string",
              "pattern": "^game:",
              "description": "A game:{id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 1,
              "description": "The previous seq + 1. A jump means a gap: apply the gap rule."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Kelly published the fact (a delta keeps its publisher's emit_ts, so lag is measurable)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": "object",
              "description": "The event's payload, every member passed through the rights gate. Clients ignore unknown members."
            }
          }
        }
      },
      "market_snapshot": {
        "name": "market_snapshot",
        "title": "Market snapshot",
        "summary": "The market topic's full state with its last_seq, expected latency and liveness.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts",
            "rg_line"
          ],
          "properties": {
            "op": {
              "const": "snapshot",
              "description": "Always `snapshot`."
            },
            "type": {
              "const": "market.snapshot",
              "description": "Always `market.snapshot`."
            },
            "topic": {
              "type": "string",
              "pattern": "^market:",
              "description": "A market:{market_id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 0,
              "description": "The topic's last_seq (STR-9): the next delta is last_seq + 1. A topic with no events yet has seq 0 and data null."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Wire sent the snapshot (Wire's own time)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": [
                "object",
                "null"
              ],
              "description": "The topic's latest state, every member passed through the rights gate for this connection's surface; null for a topic with no events yet. Clients ignore unknown members."
            },
            "expected_latency": {
              "$ref": "#/components/schemas/ExpectedLatency"
            },
            "liveness": {
              "$ref": "#/components/schemas/Liveness"
            },
            "rg_line": {
              "type": "string",
              "description": "The cost line (COMP-1, D19): 21+ and the helpline. Present on every snapshot of a wagering topic (market); show it with anything built on one."
            }
          }
        }
      },
      "market_delta": {
        "name": "market_delta",
        "title": "Market delta",
        "summary": "The market topic's one change; seq is the previous seq + 1.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "delta",
              "description": "Always `delta`."
            },
            "type": {
              "enum": [
                "market.update",
                "market.suspended",
                "market.state"
              ],
              "description": "Event types this topic carries; ignore unknown values."
            },
            "topic": {
              "type": "string",
              "pattern": "^market:",
              "description": "A market:{market_id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 1,
              "description": "The previous seq + 1. A jump means a gap: apply the gap rule."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Kelly published the fact (a delta keeps its publisher's emit_ts, so lag is measurable)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": "object",
              "description": "The event's payload, every member passed through the rights gate. Clients ignore unknown members."
            }
          }
        }
      },
      "player_snapshot": {
        "name": "player_snapshot",
        "title": "Player snapshot",
        "summary": "The player topic's full state with its last_seq, expected latency and liveness.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "snapshot",
              "description": "Always `snapshot`."
            },
            "type": {
              "const": "player.snapshot",
              "description": "Always `player.snapshot`."
            },
            "topic": {
              "type": "string",
              "pattern": "^player:",
              "description": "A player:{id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 0,
              "description": "The topic's last_seq (STR-9): the next delta is last_seq + 1. A topic with no events yet has seq 0 and data null."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Wire sent the snapshot (Wire's own time)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": [
                "object",
                "null"
              ],
              "description": "The topic's latest state, every member passed through the rights gate for this connection's surface; null for a topic with no events yet. Clients ignore unknown members."
            },
            "expected_latency": {
              "$ref": "#/components/schemas/ExpectedLatency"
            },
            "liveness": {
              "$ref": "#/components/schemas/Liveness"
            }
          }
        }
      },
      "player_delta": {
        "name": "player_delta",
        "title": "Player delta",
        "summary": "The player topic's one change; seq is the previous seq + 1.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "delta",
              "description": "Always `delta`."
            },
            "type": {
              "enum": [
                "player.update",
                "player.injury"
              ],
              "description": "Event types this topic carries; ignore unknown values."
            },
            "topic": {
              "type": "string",
              "pattern": "^player:",
              "description": "A player:{id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 1,
              "description": "The previous seq + 1. A jump means a gap: apply the gap rule."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Kelly published the fact (a delta keeps its publisher's emit_ts, so lag is measurable)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": "object",
              "description": "The event's payload, every member passed through the rights gate. Clients ignore unknown members."
            }
          }
        }
      },
      "board_snapshot": {
        "name": "board_snapshot",
        "title": "Board snapshot",
        "summary": "The board topic's full state with its last_seq, expected latency and liveness.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "snapshot",
              "description": "Always `snapshot`."
            },
            "type": {
              "const": "board.snapshot",
              "description": "Always `board.snapshot`."
            },
            "topic": {
              "type": "string",
              "pattern": "^board:",
              "description": "A board:{id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 0,
              "description": "The topic's last_seq (STR-9): the next delta is last_seq + 1. A topic with no events yet has seq 0 and data null."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Wire sent the snapshot (Wire's own time)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": [
                "object",
                "null"
              ],
              "description": "The topic's latest state, every member passed through the rights gate for this connection's surface; null for a topic with no events yet. Clients ignore unknown members."
            },
            "expected_latency": {
              "$ref": "#/components/schemas/ExpectedLatency"
            },
            "liveness": {
              "$ref": "#/components/schemas/Liveness"
            }
          }
        }
      },
      "board_delta": {
        "name": "board_delta",
        "title": "Board delta",
        "summary": "The board topic's one change; seq is the previous seq + 1.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "delta",
              "description": "Always `delta`."
            },
            "type": {
              "enum": [
                "board.update"
              ],
              "description": "Event types this topic carries; ignore unknown values."
            },
            "topic": {
              "type": "string",
              "pattern": "^board:",
              "description": "A board:{id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 1,
              "description": "The previous seq + 1. A jump means a gap: apply the gap rule."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Kelly published the fact (a delta keeps its publisher's emit_ts, so lag is measurable)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": "object",
              "description": "The event's payload, every member passed through the rights gate. Clients ignore unknown members."
            }
          }
        }
      },
      "reference_snapshot": {
        "name": "reference_snapshot",
        "title": "Reference snapshot",
        "summary": "The reference topic's full state with its last_seq, expected latency and liveness.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "snapshot",
              "description": "Always `snapshot`."
            },
            "type": {
              "const": "reference.snapshot",
              "description": "Always `reference.snapshot`."
            },
            "topic": {
              "type": "string",
              "pattern": "^reference:",
              "description": "A reference:{topic_id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 0,
              "description": "The topic's last_seq (STR-9): the next delta is last_seq + 1. A topic with no events yet has seq 0 and data null."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Wire sent the snapshot (Wire's own time)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": [
                "object",
                "null"
              ],
              "description": "The topic's latest state, every member passed through the rights gate for this connection's surface; null for a topic with no events yet. Clients ignore unknown members."
            },
            "expected_latency": {
              "$ref": "#/components/schemas/ExpectedLatency"
            },
            "liveness": {
              "$ref": "#/components/schemas/Liveness"
            }
          }
        }
      },
      "reference_delta": {
        "name": "reference_delta",
        "title": "Reference delta",
        "summary": "The reference topic's one change; seq is the previous seq + 1.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "delta",
              "description": "Always `delta`."
            },
            "type": {
              "enum": [
                "reference.revised"
              ],
              "description": "Event types this topic carries; ignore unknown values."
            },
            "topic": {
              "type": "string",
              "pattern": "^reference:",
              "description": "A reference:{topic_id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 1,
              "description": "The previous seq + 1. A jump means a gap: apply the gap rule."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Kelly published the fact (a delta keeps its publisher's emit_ts, so lag is measurable)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": "object",
              "description": "The event's payload, every member passed through the rights gate. Clients ignore unknown members."
            }
          }
        }
      },
      "league_snapshot": {
        "name": "league_snapshot",
        "title": "League snapshot",
        "summary": "The league topic's full state with its last_seq, expected latency and liveness.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "snapshot",
              "description": "Always `snapshot`."
            },
            "type": {
              "const": "league.snapshot",
              "description": "Always `league.snapshot`."
            },
            "topic": {
              "type": "string",
              "pattern": "^league:",
              "description": "A league:{id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 0,
              "description": "The topic's last_seq (STR-9): the next delta is last_seq + 1. A topic with no events yet has seq 0 and data null."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Wire sent the snapshot (Wire's own time)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": [
                "object",
                "null"
              ],
              "description": "The topic's latest state, every member passed through the rights gate for this connection's surface; null for a topic with no events yet. Clients ignore unknown members."
            },
            "expected_latency": {
              "$ref": "#/components/schemas/ExpectedLatency"
            },
            "liveness": {
              "$ref": "#/components/schemas/Liveness"
            }
          }
        }
      },
      "league_delta": {
        "name": "league_delta",
        "title": "League delta",
        "summary": "The league topic's one change; seq is the previous seq + 1.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "delta",
              "description": "Always `delta`."
            },
            "type": {
              "enum": [
                "league.update"
              ],
              "description": "Event types this topic carries; ignore unknown values."
            },
            "topic": {
              "type": "string",
              "pattern": "^league:",
              "description": "A league:{id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 1,
              "description": "The previous seq + 1. A jump means a gap: apply the gap rule."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Kelly published the fact (a delta keeps its publisher's emit_ts, so lag is measurable)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": "object",
              "description": "The event's payload, every member passed through the rights gate. Clients ignore unknown members."
            }
          }
        }
      },
      "answer_snapshot": {
        "name": "answer_snapshot",
        "title": "Answer snapshot",
        "summary": "The answer topic's full state with its last_seq, expected latency and liveness.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "snapshot",
              "description": "Always `snapshot`."
            },
            "type": {
              "const": "answer.snapshot",
              "description": "Always `answer.snapshot`."
            },
            "topic": {
              "type": "string",
              "pattern": "^answer:",
              "description": "A answer:{id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 0,
              "description": "The topic's last_seq (STR-9): the next delta is last_seq + 1. A topic with no events yet has seq 0 and data null."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Wire sent the snapshot (Wire's own time)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": [
                "object",
                "null"
              ],
              "description": "The topic's latest state, every member passed through the rights gate for this connection's surface; null for a topic with no events yet. Clients ignore unknown members."
            },
            "expected_latency": {
              "$ref": "#/components/schemas/ExpectedLatency"
            },
            "liveness": {
              "$ref": "#/components/schemas/Liveness"
            }
          }
        }
      },
      "answer_delta": {
        "name": "answer_delta",
        "title": "Answer delta",
        "summary": "The answer topic's one change; seq is the previous seq + 1.",
        "x-kelly-object": "wire_message",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "type",
            "topic",
            "seq",
            "emit_ts"
          ],
          "properties": {
            "op": {
              "const": "delta",
              "description": "Always `delta`."
            },
            "type": {
              "enum": [
                "answer.started",
                "answer.delta",
                "answer.citation",
                "answer.completed",
                "answer.figure",
                "answer.changed"
              ],
              "description": "Event types this topic carries; ignore unknown values."
            },
            "topic": {
              "type": "string",
              "pattern": "^answer:",
              "description": "A answer:{id} topic."
            },
            "seq": {
              "type": "integer",
              "minimum": 1,
              "description": "The previous seq + 1. A jump means a gap: apply the gap rule."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When Kelly published the fact (a delta keeps its publisher's emit_ts, so lag is measurable)."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": "object",
              "description": "The event's payload, every member passed through the rights gate. Clients ignore unknown members."
            }
          }
        }
      },
      "stream_snapshot": {
        "name": "stream_snapshot",
        "title": "Stream snapshot",
        "summary": "The topic's full state with its last_seq, as an SSE event.",
        "x-kelly-object": "wire_message",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "topic",
            "seq"
          ],
          "properties": {
            "op": {
              "const": "snapshot",
              "description": "Always `snapshot`."
            },
            "type": {
              "type": [
                "string",
                "null"
              ],
              "description": "Event type, such as game.state or answer.delta."
            },
            "topic": {
              "type": [
                "string",
                "null"
              ],
              "description": "The topic, such as game:{id}; absent on connection messages."
            },
            "seq": {
              "type": "integer",
              "minimum": 0,
              "description": "The topic's sequence number: each change is the previous + 1; the event's id."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When the message left Kelly."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": "object",
              "description": "The typed payload for this op and type."
            },
            "expected_latency": {
              "$ref": "#/components/schemas/ExpectedLatency"
            },
            "liveness": {
              "$ref": "#/components/schemas/Liveness"
            },
            "rg_line": {
              "type": "string",
              "description": "The cost line (COMP-1, D19): 21+ and the helpline. Present on every snapshot of a wagering topic (market); show it with anything built on one."
            }
          }
        }
      },
      "stream_delta": {
        "name": "stream_delta",
        "title": "Stream delta",
        "summary": "One change to the topic, as an SSE event; its id is the seq.",
        "x-kelly-object": "wire_message",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "x-kelly-object": "wire_message",
          "required": [
            "op",
            "topic",
            "seq"
          ],
          "properties": {
            "op": {
              "const": "delta",
              "description": "Always `delta`."
            },
            "type": {
              "type": [
                "string",
                "null"
              ],
              "description": "Event type, such as game.state or answer.delta."
            },
            "topic": {
              "type": [
                "string",
                "null"
              ],
              "description": "The topic, such as game:{id}; absent on connection messages."
            },
            "seq": {
              "type": "integer",
              "minimum": 0,
              "description": "The topic's sequence number: each change is the previous + 1; the event's id."
            },
            "source_ts": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time",
              "description": "When the fact happened upstream."
            },
            "emit_ts": {
              "type": "string",
              "format": "date-time",
              "description": "When the message left Kelly."
            },
            "source": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who produced the data."
            },
            "data": {
              "type": "object",
              "description": "The typed payload for this op and type."
            }
          }
        }
      }
    },
    "securitySchemes": {
      "ticket": {
        "type": "httpApiKey",
        "name": "Sec-WebSocket-Protocol",
        "in": "header",
        "description": "A single-use ticket (wtkt_…) from POST /v1/wire/tickets, POST /me/wire/tickets or get_wire_ticket. Send it in the first message (op auth) within the authentication deadline, or, in a browser, offer it as the second subprotocol kelly.ticket.{ticket}."
      }
    }
  },
  "x-kelly-subprotocol": "kelly.v1",
  "x-kelly-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.",
  "x-kelly-limits": {
    "ping_seconds": 20,
    "replay_seconds": 120,
    "auth_timeout_seconds": 5,
    "ticket_seconds": 60,
    "drain_seconds": 30,
    "lifetime_seconds": 86400,
    "idle_seconds": 60,
    "stale_seconds": 60,
    "max_frame_bytes": 65536,
    "inbound_per_second": 20,
    "inbound_burst": 40,
    "send_buffer_messages": 512,
    "send_buffer_soft": 256,
    "send_buffer_bytes": 4194304,
    "slow_consumer_grace_seconds": 10,
    "connections_per_ip": 5,
    "attempts_per_ip": 30,
    "attempts_seconds": 60,
    "credits_per_topic_hour": 2
  },
  "x-kelly-plan-limits": {
    "anonymous": {
      "wire_topics": 20,
      "wire_connections": 2
    },
    "free": {
      "wire_topics": 50,
      "wire_connections": 3
    },
    "pro": {
      "wire_topics": 200,
      "wire_connections": 5
    },
    "developer": {
      "wire_topics": 500,
      "wire_connections": 10
    },
    "scale": {
      "wire_topics": 5000,
      "wire_connections": 100
    }
  },
  "x-kelly-resume": {
    "window_seconds": 120,
    "first_message": "resume",
    "members": [
      "session_id",
      "resume_token",
      "last_seq"
    ],
    "needs_new_ticket": false,
    "token": "A new resume_token arrives in every hello; each works once.",
    "outside_window": "resume_failed for the topics outside it, each followed by a fresh snapshot; an expired session closes with 4011 and needs a new ticket."
  },
  "x-kelly-tickets": {
    "summary": "A ticket is minted over authenticated REST, presented once, and decides the surface; the host never does (D4, D34).",
    "lifetime_seconds": 60,
    "single_use": true,
    "auth_deadline_seconds": 5,
    "kinds": [
      {
        "name": "first_party",
        "mint": "POST https://askkelly.ai/me/wire/tickets",
        "credential": "A signed-in session",
        "surface": "wire_first_party",
        "per_book_prices": true,
        "origins": [
          "https://askkelly.ai",
          "https://www.askkelly.ai"
        ],
        "topics": "Any topic the policy allows"
      },
      {
        "name": "first_party_anonymous",
        "mint": "POST https://askkelly.ai/me/wire/tickets",
        "credential": "An anonymous visitor",
        "surface": "wire_external",
        "per_book_prices": false,
        "origins": [
          "https://askkelly.ai",
          "https://www.askkelly.ai"
        ],
        "topics": "Any topic the policy allows",
        "note": "Anyone can mint this ticket by sending the site's Origin, so it carries derived values only."
      },
      {
        "name": "api",
        "mint": "POST https://api.askkelly.ai/v1/wire/tickets",
        "credential": "An API key with the wire:connect scope, or an OAuth token",
        "surface": "wire_external",
        "per_book_prices": false,
        "origins": null,
        "topics": "Any topic the policy allows",
        "note": "A bearer secret its owner chose to hand to a page, so it has no origin binding."
      },
      {
        "name": "card",
        "mint": "get_wire_ticket on a cards path (app-only MCP tool)",
        "credential": "The host's anonymous or OAuth caller",
        "surface": "card_topic",
        "per_book_prices": false,
        "origins": [
          "https://*.claudemcpcontent.com"
        ],
        "topics": "Bound to the topics named when it was minted (STR-24); a private answer is also bound to the OAuth subject"
      },
      {
        "name": "sse",
        "mint": "Any of the above, for transport sse",
        "credential": "As the kind it was minted as",
        "surface": "As the kind it was minted as",
        "origins": "As the kind it was minted as",
        "topics": "Bound to the one topic in the URL (STR-24)"
      }
    ],
    "in_band_refresh": "A new ticket sent as auth with a request_id refreshes the credential without reconnecting; it is answered by an ack (STR-22).",
    "test_mode": "Any credential may ask for {\"mode\": \"test\"} when it mints; the ticket then opens the test stream."
  },
  "x-kelly-card-origins": {
    "first_party": [
      "https://askkelly.ai",
      "https://www.askkelly.ai"
    ],
    "cards": [],
    "card_suffixes": [
      ".claudemcpcontent.com"
    ],
    "refused_with": 4013,
    "rule": "A first-party or card ticket accepts only its own origins, checked against the connection's Origin header; any other origin, or none, closes with 4013. An API ticket has no origin binding. Origin is defence in depth: the ticket is the authorization (STR-6)."
  },
  "x-kelly-sse": {
    "request": "GET https://api.askkelly.ai/v1/stream/{topic}",
    "event_id": "The delta's seq",
    "resume_header": "Last-Event-ID",
    "resume_seconds": 120,
    "ticket_query_parameter": "ticket",
    "ticket_seconds": 60,
    "refused_with": 401,
    "last_resort": "A card whose host blocks both sockets and SSE polls get_card_updates at the topic's cadence, only while it is on screen."
  },
  "x-kelly-test-stream": {
    "served_to": "Test-mode credentials: keys with the kelly_test_ prefix, or any credential that asks for {\"mode\": \"test\"} when it mints a ticket.",
    "content": "The recorded Sunday, public sample data, played on a loop on a deterministic clock.",
    "speed": 10,
    "expected_latency_basis": "test_stream",
    "seq": "Keeps rising across loops; the jump back to the start is answered by the gap rule with a fresh snapshot.",
    "hello_mode": "test",
    "debug_reconnects": {
      "query": "debug_reconnects=1",
      "drain_every_seconds": 30
    }
  },
  "x-kelly-rights": {
    "register": "registry/rights.yaml",
    "rule": "Every member of every snapshot and delta is checked against the rights register for the connection's surface; a member with no register entry is not sent.",
    "per_book_prices": "Signed-in first-party tickets only (D4, D34).",
    "edges": "No edge travels on a shared topic (D21); only the answer topic, which is the answer's own, may carry one.",
    "external_topics": "Off askkelly.ai, topics carry engine-computed values only until a licence allows more.",
    "surfaces": [
      "wire_first_party",
      "wire_external",
      "card_topic"
    ]
  },
  "x-kelly-expected-latency": {
    "bases": {
      "watched_poll": "A game someone watches: the odds poller polls it every 30 seconds (D23).",
      "scheduled": "A batch feed with a published schedule, such as a nightly or hourly sweep.",
      "paused": "The feed is paused because the month's credits are spent; the figure holds its last value.",
      "computed": "Computed by Kelly from other topics, so it follows their feeds.",
      "test_stream": "The recorded Sunday played on a loop (test mode).",
      "none": "No live feed sits behind this topic."
    },
    "liveness": {
      "live": "Updates are arriving within the topic's limit; a live step may rely on it.",
      "stale": "No update arrived within the topic's limit (expected latency plus stale_seconds).",
      "not_tracked": "The topic has no live feed (basis scheduled, paused or none); there is nothing to go stale."
    },
    "stale_seconds": 60
  },
  "x-kelly-close-ranges": [
    {
      "from": 4000,
      "to": 4099,
      "name": "fatal",
      "meaning": "Fatal for this request: do not reconnect unchanged."
    },
    {
      "from": 4100,
      "to": 4199,
      "name": "back_off",
      "meaning": "Back off: reconnect after at least 1 second, with exponential backoff and jitter."
    },
    {
      "from": 4200,
      "to": 4299,
      "name": "reconnect",
      "meaning": "Reconnect now and resume immediately."
    }
  ],
  "x-kelly-close-codes": [
    {
      "code": 4001,
      "name": "malformed_frame",
      "range": "fatal",
      "meaning": "A frame was not a valid envelope."
    },
    {
      "code": 4002,
      "name": "idle",
      "range": "fatal",
      "meaning": "The connection held no topic for the idle limit; connect again when there is one."
    },
    {
      "code": 4009,
      "name": "frame_too_large",
      "range": "fatal",
      "meaning": "A frame exceeded the published maximum size (STR-14)."
    },
    {
      "code": 4010,
      "name": "auth_timeout",
      "range": "fatal",
      "meaning": "No ticket arrived within the authentication deadline."
    },
    {
      "code": 4011,
      "name": "auth_failed",
      "range": "fatal",
      "meaning": "The ticket was invalid, expired or already used."
    },
    {
      "code": 4012,
      "name": "unsupported_version",
      "range": "fatal",
      "meaning": "The subprotocol version is not served."
    },
    {
      "code": 4013,
      "name": "origin_not_allowed",
      "range": "fatal",
      "meaning": "A browser connected from an origin outside the allowlist."
    },
    {
      "code": 4108,
      "name": "slow_consumer",
      "range": "back_off",
      "meaning": "The send buffer overflowed after a warning (STR-13)."
    },
    {
      "code": 4110,
      "name": "unavailable",
      "range": "back_off",
      "meaning": "A dependency of the server (Redis or the database) failed: back off and reconnect; resume works once it is back."
    },
    {
      "code": 4129,
      "name": "rate_limited",
      "range": "back_off",
      "meaning": "Too many messages or connection attempts in the window (STR-14)."
    },
    {
      "code": 4131,
      "name": "too_many_connections",
      "range": "back_off",
      "meaning": "The address or credential already holds its published number of connections (STR-14, STR-15)."
    },
    {
      "code": 4200,
      "name": "drain",
      "range": "reconnect",
      "meaning": "Planned shutdown or the 24-hour lifetime cap, after a drain notice (STR-16, STR-23)."
    },
    {
      "code": 4201,
      "name": "heartbeat_timeout",
      "range": "reconnect",
      "meaning": "Pings went unanswered."
    }
  ],
  "x-kelly-surface": "wire_external",
  "x-generated-by": "python -m registry.generate --out registry/generated"
}
