realtimeodds-spec
v0.5.0
Published
JSON Schema specifications for the realtimeodds public protocol — source of truth for all SDK ports.
Maintainers
Readme
realtimeodds-spec
JSON Schema specifications for the realtimeodds public protocol — source of truth for all SDK ports.
Status: protocol v1, MVP scope.
What this is
A language-agnostic contract describing the public surface of realtimeodds, a unified API to consume real-time betting odds from multiple bookmakers. The spec covers two distinct surfaces:
- Wire protocol (
wire/) — what the gateway transmits to a connected SDK over WebSocket. - SDK events (
sdk_events/) — what each SDK port emits to its consumer after translating wire messages.
Plus shared definitions: entity schemas (SportEvent, Market, Selection, Quote, OrderBook), enums (common.schema.json), and authentication (auth.schema.json).
Layout
schemas/v1/
├── common.schema.json # shared enums, ids, timestamps
├── sport_event.schema.json # SportEvent (discriminated by `kind`)
├── market.schema.json # Market (discriminated by `kind`)
├── selection.schema.json
├── quote.schema.json
├── order_book.schema.json
├── auth.schema.json
├── sdk_events/ # SDK → consumer (uses `bookmaker`)
│ ├── sport_event_added.schema.json
│ ├── sport_event_updated.schema.json
│ ├── sport_event_removed.schema.json
│ ├── odds_changed.schema.json
│ └── lifecycle.schema.json
└── wire/ # gateway → SDK (uses `source` = preset id)
├── hello.schema.json
├── snapshot.schema.json
├── preset_list.schema.json
├── new_event.schema.json
├── update_event.schema.json
├── prices_updated.schema.json
├── remove_event.schema.json
├── store_cleared.schema.json
├── preset_snapshot.schema.json
├── preset_updated.schema.json
├── preset_removed.schema.json
└── fetcher_status.schema.json
examples/
├── sdk_events/
└── wire/Wire protocol (gateway → SDK)
Transport
WebSocket. The client connects with its API key in the query string:
wss://gateway.example.com/?apiKey=<key>On invalid auth, the server closes the connection before any handshake with one of:
| Close code | Meaning | |---|---| | 4001 | missing apiKey | | 4002 | invalid apiKey | | 4003 | quota / rate-limit exceeded (reserved — not enforced in current MVP) |
API keys are issued out-of-band (manual config edit on the gateway for MVP).
Message ordering
After a successful WebSocket open, the gateway streams messages in this strict order:
hello— protocol handshake. MUST be the first message. Clients that receive anything else first MUST refuse the connection.snapshot— full current state across all running fetcher sources.preset_list— list of configured fetcher presets.- Mutations (any order):
new_event,update_event,prices_updated,remove_event,store_cleared,fetcher_status,preset_updated,preset_removed.
Source identifier
Wire messages that reference a fetcher carry source — the preset id. In current deployments the preset id often equals a Bookmaker name (e.g. "ps3838"), but it is conceptually a preset identifier. SDK ports translate source into a Bookmaker value for the public API, since the bookmaker is also recoverable from the SportEventId.
SDK events (SDK → consumer)
Each SDK port emits these events locally to its consumer after translating the wire feed:
| Event | Payload |
|---|---|
| sportEvent:added | { sportEvent } — first observation of a sport event. |
| sportEvent:updated | { sportEvent } — any change (metadata, market list, or any odds value). |
| sportEvent:removed | { bookmaker, sportEventId } — no longer reported. |
| odds:changed | { bookmaker, sportEventId, marketId, selectionId, quote?, orderBook? } — per-selection price update. Fires alongside sportEvent:updated. |
Plus connection lifecycle events: connected, disconnected, reconnecting, error (see sdk_events/lifecycle.schema.json).
Entities
SportEvent, Market, Selection, Quote, OrderBook describe the wire JSON shape. Two important conventions:
bookmakeris not a wire field. It is recoverable fromSportEventId(formatvmid:<bookmaker>:<external_id>). SDK ports typically expose it as a computed property on theSportEventclass.sportis not a wire field. It is recoverable fromSportEventKind(se:basketball_match→basketball). SDK ports typically expose it as a computed property too.
This keeps the wire format minimal and avoids field duplication.
SportEvent is discriminated by kind (e.g. se:basketball_match); each variant adds sport-specific fields. Market is discriminated by kind (e.g. market:basketball_match.moneyline).
Multi-source
The same underlying match (e.g. Lakers vs Celtics) reported by two bookmakers shows up as two distinct SportEvent instances with different id (and therefore different bookmaker). Cross-source comparison is a consumer concern.
Versioning
The protocol is versioned independently of any SDK port. The current major is v1 (folder schemas/v1/). Backwards-incompatible changes will introduce v2/ alongside, never silently mutate v1/. Within a major, all changes are strictly additive — see the additive-only invariant in sb-entities/PROTOCOL.md.
SDK ports
realtimeodds-js— TypeScript / JavaScript (Node + Browser)realtimeodds-python— planned (next port)realtimeodds-go— planned
License
MIT — see LICENSE.
