DRAFT v0.3 / Payload catalog

Every message has one job.

The payload catalog separates connection control, complete state, market deltas, and failures. The small vocabulary is intended to keep hot-path routing predictable.

Catalog

session.readyAuthentication accepted and session capabilities established.CONTROL
subscription.readyFilters accepted and a subscription ID assigned.CONTROL
state.snapshotComplete subscription state at one sequence.STATE
odds.upsertCreate or fully replace one current quote.DELTA
odds.removeRemove one quote from current state.DELTA
event.updateApply a small event lifecycle change.DELTA
session.heartbeatConfirm liveness and the latest stream position.CONTROL
request.errorDescribe a rejected client action.CONTROL
SHARED / ENVELOPE

Message envelope

{
  "type": "odds.upsert",
  "schema_version": "0.3",
  "message_id": "msg_01J5Y7M4T6",
  "sequence": 18492,
  "emitted_at": "2026-08-07T05:20:00.438Z",
  "data": { ... }
}
typestringPayload discriminator.
schema_versionstringContract version required to decode the message.
message_idstringUnique tracing and deduplication identifier.
sequenceintegerOrdered stream position.
emitted_atRFC 3339Time OddsLoom emitted the frame.
dataobjectShape selected by type.
STATE

state.snapshot

Complete state for the accepted filters. Replace local subscription state atomically, commit its sequence, then process later deltas.

{
  "type": "state.snapshot",
  "schema_version": "0.3",
  "message_id": "msg_01J5Y7M0KJ",
  "sequence": 18491,
  "emitted_at": "2026-08-07T05:20:00.120Z",
  "data": {
    "subscription_id": "sub_01J5Y7KZXN",
    "sports": [ ... ],
    "leagues": [ ... ],
    "participants": [ ... ],
    "events": [ ... ],
    "markets": [ ... ],
    "books": [ ... ],
    "quotes": [ ... ]
  }
}
DELTA

odds.upsert

Create or replace the quote with this ID. The payload is deliberately a full quote rather than a partial patch.

{
  "type": "odds.upsert",
  "schema_version": "0.3",
  "message_id": "msg_01J5Y7M4T6",
  "sequence": 18492,
  "emitted_at": "2026-08-07T05:20:00.438Z",
  "data": {
    "subscription_id": "sub_01J5Y7KZXN",
    "quote": {
      "id": "quote_book_a_market_123_outcome_min",
      "market_id": "market_evt_min_den_full_game_spread",
      "outcome_id": "outcome_min",
      "book_id": "book_a",
      "availability": "open",
      "line": 5.5,
      "price": { "american": -105, "decimal": 1.9524 },
      "observed_at": "2026-08-07T05:20:00.432Z"
    }
  }
}
DELTA

odds.remove

Delete the quote from current state. Temporary unavailability should normally arrive as an upsert with availability: suspended, not removal.

{
  "type": "odds.remove",
  "schema_version": "0.3",
  "message_id": "msg_01J5Y7M8PQ",
  "sequence": 18493,
  "emitted_at": "2026-08-07T05:20:01.014Z",
  "data": {
    "subscription_id": "sub_01J5Y7KZXN",
    "quote_id": "quote_book_a_market_123_outcome_min",
    "reason": "offer_withdrawn",
    "observed_at": "2026-08-07T05:20:01.009Z"
  }
}
quote_idstringQuote to remove.
reasonenumoffer_withdrawn, market_closed, event_closed, correction, or source_unavailable.
observed_atRFC 3339Time the removal condition was observed.
DELTA

event.update

Updates event lifecycle fields without resending its participants or market definitions. Only keys present in changes are replaced.

{
  "type": "event.update",
  "schema_version": "0.3",
  "message_id": "msg_01J5Y7N13C",
  "sequence": 18494,
  "emitted_at": "2026-08-07T05:20:02.201Z",
  "data": {
    "event_id": "event_nba_20260808_min_den",
    "changes": {
      "phase": "live",
      "status": "active"
    },
    "observed_at": "2026-08-07T05:20:02.190Z"
  }
}
CONTROL

session.heartbeat

A heartbeat proves the connection is live and advances no domain state. Its envelope sequence reports the latest position known to the session.

{
  "type": "session.heartbeat",
  "schema_version": "0.3",
  "message_id": "msg_01J5Y7QHB4",
  "sequence": 18512,
  "emitted_at": "2026-08-07T05:20:15.000Z",
  "data": { "session_id": "ses_01J5Y7K1Y7" }
}
CONTROL

request.error

A request-scoped failure does not necessarily close the session. Consumers should branch on code and retryable, not parse message text.

{
  "type": "request.error",
  "schema_version": "0.3",
  "message_id": "msg_01J5Y7RXE0",
  "sequence": 18513,
  "emitted_at": "2026-08-07T05:20:16.104Z",
  "data": {
    "request_id": "req_01J5Y7RX8V",
    "code": "INVALID_FILTER",
    "message": "Unknown league key.",
    "retryable": false,
    "details": { "field": "leagues", "value": "nbaa" }
  }
}
CONTINUEUnderstand reliability