openapi: 3.2.0 info: title: Sports odds API description: 'The Odds API V1 provides sports and racing events, bookmaker odds, exchange and prediction-market order books, live price updates, betting opportunity snapshots, and result lookups for production integrations.' version: 1.0.0 license: name: Proprietary url: https://odds-api.net x-workflow-examples: - name: positive_ev_request summary: Find positive EV bets for a league. steps: - GET /v1/events?sport=rugby-league&league=NRL - GET /v1/bets/snapshot?strategies=pos_ev - Use response timestamps and stake/risk controls before showing user-facing picks. - name: arbitrage_request summary: Find arbitrage opportunities across allowed bookmakers. steps: - GET /v1/bookmakers - GET /v1/bets/snapshot?strategies=arbitrage - Show execution-risk caveats; never present arbitrage as guaranteed after limits, voids, or delays. - name: odds_history_request summary: Get line movement for a market. steps: - GET /v1/events/{event_id}/odds/snapshot - Pick a selection_key from an odds line. - GET /v1/events/{event_id}/odds/history?selection_key=... - name: prediction_market_orderbook_request summary: Read and follow a curated prediction-market order book. steps: - GET /v1/events?sport=basketball&league=NBA - GET /v1/events/{event_id}/prediction-markets/orderbook/snapshot?providers=polymarket,kalshi&depth=3 - Connect to the matching /stream or /ws route with since=. x-responsible-gambling: Do not present betting outcomes as risk-free. Always mention execution risk, stale prices, bookmaker limits, voids, delays, and jurisdictional availability in user-facing products. x-production-integration: recommended_flow: - Authenticate backend requests with X-API-Key. - Discover sports, leagues, and bookmakers before storing local filters. - Page sports or racing events with limit and cursor. - Fetch odds snapshots for initial state. Explicit bookmaker filters are complete in one response; otherwise follow bookmaker-complete pages until complete=true. Persist as_of_ts_ms, ttl_seconds, and resume. - Use SSE or WebSocket streams for realtime updates and reconnect with since=. - Use history endpoints for line movement and results endpoints after events finish. recommended_polling_intervals: catalog_metadata: 6-24h sports_events: 5-15m, or 1-5m for active leagues and near-start windows racing_events: 1-5m with narrow time windows odds_snapshots_without_streams: 60-120s, or 15-30s for priority events with strict quota controls betting_opportunity_snapshots: 30-120s depending on alert urgency results_after_start: 1-5m until settled, then back off stream_reconnects: - Snapshot first, then connect to /stream or /ws with matching filters. - Apply delta messages idempotently and persist the newest resume token. - Use heartbeat messages as liveness checks. - Reconnect with jittered exponential backoff and since=. - Reload the snapshot when a resync message indicates the resume token is unavailable. rate_limit_backoff: - Honor Retry-After on 429 when present. - Otherwise start around 2 seconds and double up to about 60 seconds with jitter. - Use X-RateLimit-* response headers when present to tune callers. - Use /usage to detect monthly API-credit quota exhaustion and stop retry loops. cache_strategy: - Cache by endpoint path, event ID, normalized filters, page cursor, and product context. - Respect ttl_seconds when present and always surface as_of_ts_ms. - Use streams to update hot caches and reload snapshots after resync or parse failure. - Prefer stale-while-revalidate displays with a visible timestamp. failure_modes: - 400 invalid request shape, cursor, timestamp, or filter. - 401 missing or invalid API key. - 403 key lacks plan, product, bookmaker, stream, racing, or strategy access. - 404 event, race, result, or selection is unavailable. - 429 rate limit or monthly quota exhaustion. - 5xx transient service failure; retry with backoff and keep last good cached data marked stale. - Stream close or resync; reconnect with since or reload the snapshot. servers: - url: https://api.odds-api.net/v1 description: Production V1 base URL. Set ODDS_API_BASE_URL to override it in SDKs and examples. security: - ApiKeyAuth: [] tags: - name: Sports odds description: Sports odds snapshots, line movement, Server-Sent Events, and WebSocket updates. paths: /events/{event_id}/odds/snapshot: get: tags: - Sports odds summary: 'Event odds: Snapshot' operationId: odds_snapshot_events__event_id__odds_snapshot_get security: - ApiKeyAuth: [] parameters: - name: event_id in: path required: true schema: type: string title: Event Id description: Canonical event or race identifier from an event list response. - name: limit in: query required: false schema: type: integer maximum: 25000 minimum: 1 description: Soft item target when bookmakers is omitted. Pages contain whole bookmakers and may exceed this target. When bookmakers is supplied, all matching lines are returned up to the response safety cap. default: 2000 title: Limit description: Soft item target when `bookmakers` is omitted. Pages contain whole bookmakers and may exceed this target. With an explicit bookmaker filter, all matching lines are returned up to the 25,000-item safety cap. - name: cursor in: query required: false schema: anyOf: - type: string - type: 'null' description: Opaque bookmaker-page cursor from next_cursor. Use only when bookmakers is omitted and keep all filters identical between pages. title: Cursor description: Opaque bookmaker-page cursor from the previous `next_cursor`. Use only when `bookmakers` is omitted and keep all filters identical between pages. - name: bookmakers in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated bookmaker allow-list. Explicitly requested bookmakers are returned complete in one response, up to the response safety cap. title: Bookmakers description: Comma-separated bookmaker allow-list. Explicitly requested bookmakers are returned complete in one response, up to the 25,000-item safety cap. Use `/bookmakers` to discover supported keys. - name: types in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated type allow-list title: Types description: Comma-separated market type allow-list for odds filters. - name: market_keys in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated market_key allow-list title: Market Keys description: Comma-separated market key allow-list for odds filters. - name: periods in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated periods allow-list title: Periods description: Comma-separated period allow-list for odds filters. - name: price_fields in: query required: false schema: anyOf: - type: string - type: 'null' title: Price Fields description: '`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields.' - name: include_source in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Source description: When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept. - name: include_debug_ids in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Debug Ids description: When true, include internal/subgroup/opposing IDs useful for reconciliation. - name: include_unavailable in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Unavailable description: When true, include unavailable or suspended rows where the endpoint supports them. responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/OddsSnapshotResponse' examples: default: summary: 'Event odds: Snapshot' value: event_id: '3704597661' as_of_ts_ms: 1760000000000 snapshot_capture_ts_ms: 1759999999800 bookmaker_as_of_ts_ms: bet365: 1760000000000 oldest_bookmaker_as_of_ts_ms: 1760000000000 target_refresh_interval_seconds: 60 ttl_seconds: 1800 items: - id: 'bet365::moneyline::moneyline::0::::home::' event_id: '3704597661' bookmaker: bet365 market_key: moneyline bet_type: moneyline period: full time side: home selection_name: Home odds: 2.1 fair_odds: 1.98 is_available: true next_cursor: null complete: true bookmakers_included: - bet365 bookmaker_counts: bet365: 1 resume: 1760000000000-0 '413': description: The requested bookmaker-complete snapshot exceeds the response safety cap. '400': description: Invalid request parameters or body. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Missing or invalid credentials. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Credentials are valid but do not allow this resource. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Resource was not found. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': description: Rate limit exceeded. headers: Retry-After: description: Seconds to wait before retrying when supplied by the limiter. schema: type: integer minimum: 1 X-RateLimit-Limit: description: Request bucket capacity for the active limiter bucket when supplied. schema: type: integer X-RateLimit-Remaining: description: Approximate remaining requests in the active limiter bucket when supplied. schema: type: integer minimum: 0 X-RateLimit-Bucket: description: Limiter bucket name that produced the response when supplied. schema: type: string content: application/json: schema: $ref: '#/components/schemas/RateLimitResponse' '500': description: Unexpected server error. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' description: Returns the current odds lines for one event. Use filters to narrow bookmakers, market types, market keys, and periods. An explicit `bookmakers` filter returns every matching line for those bookmakers in one response, up to the 25,000-item safety cap. Without a bookmaker filter, pages contain whole bookmakers, so a bookmaker's matching markets are never split across pages. Follow the opaque `next_cursor` until `complete=true`. Cache by request shape. `as_of_ts_ms` is when the API accepted the latest successful event snapshot or authoritative bookmaker subset; use `bookmaker_as_of_ts_ms` for bookmaker-specific freshness and compare it with `target_refresh_interval_seconds`. Respect `ttl_seconds` when present, and persist `resume` when you plan to subscribe to updates. Pass `price_fields=odds,fair` to include nullable composite fair odds alongside bookmaker odds. Exchange orderbooks are excluded from this sportsbook-style odds surface. x-rate-limit-note: Handle HTTP 429 with backoff and avoid tight polling loops. /events/{event_id}/odds/stream: get: tags: - Sports odds summary: 'Event odds: Stream (SSE)' operationId: odds_stream_events__event_id__odds_stream_get security: - ApiKeyAuth: [] parameters: - name: event_id in: path required: true schema: type: string title: Event Id description: Canonical event or race identifier from an event list response. - name: bookmakers in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated bookmaker allow-list title: Bookmakers description: Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys. - name: types in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated type allow-list title: Types description: Comma-separated market type allow-list for odds filters. - name: market_keys in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated market_key allow-list title: Market Keys description: Comma-separated market key allow-list for odds filters. - name: periods in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated periods allow-list title: Periods description: Comma-separated period allow-list for odds filters. - name: price_fields in: query required: false schema: anyOf: - type: string - type: 'null' title: Price Fields description: '`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields.' - name: include_source in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Source description: When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept. - name: include_debug_ids in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Debug Ids description: When true, include internal/subgroup/opposing IDs useful for reconciliation. - name: include_unavailable in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Unavailable description: When true, include unavailable or suspended rows where the endpoint supports them. - name: since in: query required: false schema: anyOf: - type: string - type: 'null' description: Redis stream ID title: Since description: Resume token from a previous snapshot or stream message. Pass it after reconnecting. - name: catchup in: query required: false schema: type: boolean default: true title: Catchup description: When true, return available missed stream events after `since` before waiting for new events. - name: heartbeat_sec in: query required: false schema: type: integer maximum: 120 minimum: 5 default: 15 title: Heartbeat Sec description: Heartbeat interval in seconds for stream liveness. Valid range is 5-120. - name: max_batch in: query required: false schema: type: integer maximum: 2000 minimum: 1 default: 500 title: Max Batch description: Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients. responses: '200': description: Server-Sent Events stream. Each message has an event name and JSON data payload. content: text/event-stream: schema: type: object description: Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON. required: - event - data properties: event: type: string enum: - delta - heartbeat - resync data: oneOf: - $ref: '#/components/schemas/OddsStreamEvent' - $ref: '#/components/schemas/StreamHeartbeat' - $ref: '#/components/schemas/StreamResyncEvent' examples: delta: summary: Decoded delta message value: event: delta data: event_id: '3704597661' resume: 1760000000000-0 snapshot_id: capture-123:3704597661 batch_index: 1 batch_count: 1 changes: [] heartbeat: summary: Decoded heartbeat message value: event: heartbeat data: {} resync: summary: Decoded resync message value: event: resync data: event_id: '3704597661' resume: null reason: trimmed '400': description: Invalid request parameters or body. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Missing or invalid credentials. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Credentials are valid but do not allow this resource. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Resource was not found. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': description: Rate limit exceeded. headers: Retry-After: description: Seconds to wait before retrying when supplied by the limiter. schema: type: integer minimum: 1 X-RateLimit-Limit: description: Request bucket capacity for the active limiter bucket when supplied. schema: type: integer X-RateLimit-Remaining: description: Approximate remaining requests in the active limiter bucket when supplied. schema: type: integer minimum: 0 X-RateLimit-Bucket: description: Limiter bucket name that produced the response when supplied. schema: type: string content: application/json: schema: $ref: '#/components/schemas/RateLimitResponse' '500': description: Unexpected server error. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' description: Server-Sent Events feed for odds changes on one event. Subscribe after reading the snapshot and pass the snapshot `resume` value as `since` to receive catch-up changes when available. Handle ordered semantic `delta` batches idempotently and persist each batch's `resume`; a `heartbeat` carries current freshness even when prices did not change. Reload the snapshot after `resync`. Exchange orderbook changes are served only from the exchange orderbook stream. x-rate-limit-note: Handle HTTP 429 with backoff and avoid tight polling loops. x-stream-metering: stream_units: 1 stream_hours_formula: stream_units * open_seconds / 3600 logical_bytes_metered: true quota_policy: hard_cap applies_to: authenticated API-key connections /events/{event_id}/odds/ws: get: operationId: websocket_events_event_id_odds_ws parameters: - name: event_id in: path required: true schema: type: string title: Event Id description: Canonical event or race identifier from an event list response. - name: bookmakers in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated bookmaker allow-list title: Bookmakers description: Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys. - name: types in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated type allow-list title: Types description: Comma-separated market type allow-list for odds filters. - name: market_keys in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated market_key allow-list title: Market Keys description: Comma-separated market key allow-list for odds filters. - name: periods in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated periods allow-list title: Periods description: Comma-separated period allow-list for odds filters. - name: price_fields in: query required: false schema: anyOf: - type: string - type: 'null' title: Price Fields description: '`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields.' - name: include_source in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Source description: When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept. - name: include_debug_ids in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Debug Ids description: When true, include internal/subgroup/opposing IDs useful for reconciliation. - name: include_unavailable in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Unavailable description: When true, include unavailable or suspended rows where the endpoint supports them. - name: since in: query required: false schema: anyOf: - type: string - type: 'null' description: Redis stream ID title: Since description: Resume token from a previous snapshot or stream message. Pass it after reconnecting. - name: catchup in: query required: false schema: type: boolean default: true title: Catchup description: When true, return available missed stream events after `since` before waiting for new events. - name: heartbeat_sec in: query required: false schema: type: integer maximum: 120 minimum: 5 default: 15 title: Heartbeat Sec description: Heartbeat interval in seconds for stream liveness. Valid range is 5-120. - name: max_batch in: query required: false schema: type: integer maximum: 2000 minimum: 1 default: 500 title: Max Batch description: Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients. responses: '101': description: WebSocket connection established. Messages are JSON objects. content: application/json: schema: type: object description: Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON. required: - event - data properties: event: type: string enum: - delta - heartbeat - resync data: oneOf: - $ref: '#/components/schemas/OddsStreamEvent' - $ref: '#/components/schemas/StreamHeartbeat' - $ref: '#/components/schemas/StreamResyncEvent' examples: delta: summary: Delta message value: event: delta data: event_id: '3704597661' resume: 1760000000000-0 snapshot_id: capture-123:3704597661 batch_index: 1 batch_count: 1 changes: [] heartbeat: summary: Heartbeat message value: event: heartbeat data: {} resync: summary: Resync message value: event: resync data: event_id: '3704597661' resume: null reason: trimmed '400': description: Invalid request parameters or body. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Missing or invalid credentials. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Credentials are valid but do not allow this resource. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Resource was not found. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': description: Rate limit exceeded. headers: Retry-After: description: Seconds to wait before retrying when supplied by the limiter. schema: type: integer minimum: 1 X-RateLimit-Limit: description: Request bucket capacity for the active limiter bucket when supplied. schema: type: integer X-RateLimit-Remaining: description: Approximate remaining requests in the active limiter bucket when supplied. schema: type: integer minimum: 0 X-RateLimit-Bucket: description: Limiter bucket name that produced the response when supplied. schema: type: string content: application/json: schema: $ref: '#/components/schemas/RateLimitResponse' '500': description: Unexpected server error. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '200': description: OpenAPI tooling compatibility response schema. Runtime WebSocket connections upgrade with 101. content: application/json: schema: type: object description: Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON. required: - event - data properties: event: type: string enum: - delta - heartbeat - resync data: oneOf: - $ref: '#/components/schemas/OddsStreamEvent' - $ref: '#/components/schemas/StreamHeartbeat' - $ref: '#/components/schemas/StreamResyncEvent' examples: delta: summary: Delta message value: event: delta data: event_id: '3704597661' resume: 1760000000000-0 snapshot_id: capture-123:3704597661 batch_index: 1 batch_count: 1 changes: [] heartbeat: summary: Heartbeat message value: event: heartbeat data: {} resync: summary: Resync message value: event: resync data: event_id: '3704597661' resume: null reason: trimmed x-websocket: true x-stream-equivalent: /events/{event_id}/odds/stream tags: - Sports odds summary: 'Event odds: Stream (WebSocket)' description: WebSocket feed for odds changes on one event. Messages use the same `delta`, `heartbeat`, and `resync` payloads as the SSE stream. Reconnect with jittered exponential backoff and `since=`. security: - ApiKeyAuth: [] x-rate-limit-note: Handle HTTP 429 with backoff and avoid tight polling loops. x-stream-metering: stream_units: 1 stream_hours_formula: stream_units * open_seconds / 3600 logical_bytes_metered: true quota_policy: hard_cap applies_to: authenticated API-key connections /events/{event_id}/odds/history: get: tags: - Sports odds summary: 'Event odds history: Snapshot' operationId: odds_history_events__event_id__odds_history_get security: - ApiKeyAuth: [] parameters: - name: event_id in: path required: true schema: type: string title: Event Id description: Canonical event or race identifier from an event list response. - name: selection_key in: query required: true schema: type: string minLength: 1 title: Selection Key description: Stable selection identifier from an odds snapshot line, used for history and line movement. - name: market_group_id in: query required: false schema: anyOf: - type: string - type: 'null' title: Market Group Id description: Optional market grouping filter for history queries. - name: bookmakers in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated bookmaker allow-list title: Bookmakers description: Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys. - name: from_ts in: query required: false schema: anyOf: - type: string - type: 'null' description: ISO8601 UTC start title: From Ts description: ISO8601 UTC start timestamp for a bounded history query. - name: to_ts in: query required: false schema: anyOf: - type: string - type: 'null' description: ISO8601 UTC end title: To Ts description: ISO8601 UTC end timestamp for a bounded history query. - name: price_type in: query required: false schema: type: string pattern: ^(odds|odds_no_vig|fair_odds)$ default: odds title: Price Type description: History price type to return, for example odds. - name: price_fields in: query required: false schema: anyOf: - type: string - type: 'null' title: Price Fields description: '`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields.' - name: include_source in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Source description: When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept. - name: include_debug_ids in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Debug Ids description: When true, include internal/subgroup/opposing IDs useful for reconciliation. - name: include_unavailable in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Unavailable description: When true, include unavailable or suspended rows where the endpoint supports them. - name: limit_points_per_bookmaker in: query required: false schema: type: integer maximum: 10000 minimum: 1 default: 2000 title: Limit Points Per Bookmaker description: Maximum history points per bookmaker. Use this to keep chart/backtest payloads bounded. responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/OddsHistoryResponse' examples: default: summary: 'Event odds history: Snapshot' value: event_id: '3704597661' selection_key: moneyline:home price_type: odds series: - bookmaker_name: bet365 points: - tick_ts: '2026-04-29T08:00:00Z' is_available: true odds: 2.08 - tick_ts: '2026-04-29T08:05:00Z' is_available: true odds: 2.1 meta: from_ts: '2026-04-29T08:00:00Z' to_ts: '2026-04-29T09:00:00Z' available_price_types: - odds - odds_no_vig - fair_odds '400': description: Invalid request parameters or body. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Missing or invalid credentials. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Credentials are valid but do not allow this resource. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Resource was not found. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': description: Rate limit exceeded. headers: Retry-After: description: Seconds to wait before retrying when supplied by the limiter. schema: type: integer minimum: 1 X-RateLimit-Limit: description: Request bucket capacity for the active limiter bucket when supplied. schema: type: integer X-RateLimit-Remaining: description: Approximate remaining requests in the active limiter bucket when supplied. schema: type: integer minimum: 0 X-RateLimit-Bucket: description: Limiter bucket name that produced the response when supplied. schema: type: string content: application/json: schema: $ref: '#/components/schemas/RateLimitResponse' '500': description: Unexpected server error. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' description: Returns line movement for a single selection across bookmakers and a time range. Use `selection_key` from an odds snapshot response, bound queries with `from_ts` and `to_ts`, and narrow by bookmaker or market when building charts or backtests. x-rate-limit-note: Handle HTTP 429 with backoff and avoid tight polling loops. /events/{event_id}/odds/history/stream: get: tags: - Sports odds summary: 'Event odds history: Stream (SSE)' operationId: odds_history_stream_events__event_id__odds_history_stream_get security: - ApiKeyAuth: [] parameters: - name: event_id in: path required: true schema: type: string title: Event Id description: Canonical event or race identifier from an event list response. - name: selection_key in: query required: true schema: type: string minLength: 1 title: Selection Key description: Stable selection identifier from an odds snapshot line, used for history and line movement. - name: market_group_id in: query required: false schema: anyOf: - type: string - type: 'null' title: Market Group Id description: Optional market grouping filter for history queries. - name: bookmakers in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated bookmaker allow-list title: Bookmakers description: Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys. - name: price_type in: query required: false schema: type: string pattern: ^(odds|odds_no_vig|fair_odds)$ default: odds_no_vig title: Price Type description: History price type to return, for example odds. - name: price_fields in: query required: false schema: anyOf: - type: string - type: 'null' title: Price Fields description: '`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields.' - name: include_source in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Source description: When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept. - name: include_debug_ids in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Debug Ids description: When true, include internal/subgroup/opposing IDs useful for reconciliation. - name: include_unavailable in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Unavailable description: When true, include unavailable or suspended rows where the endpoint supports them. - name: since in: query required: false schema: anyOf: - type: string - type: 'null' description: Redis stream ID title: Since description: Resume token from a previous snapshot or stream message. Pass it after reconnecting. - name: catchup in: query required: false schema: type: boolean default: true title: Catchup description: When true, return available missed stream events after `since` before waiting for new events. - name: heartbeat_sec in: query required: false schema: type: integer maximum: 120 minimum: 5 default: 15 title: Heartbeat Sec description: Heartbeat interval in seconds for stream liveness. Valid range is 5-120. - name: max_batch in: query required: false schema: type: integer maximum: 2000 minimum: 1 default: 500 title: Max Batch description: Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients. responses: '200': description: Server-Sent Events stream. Each message has an event name and JSON data payload. content: text/event-stream: schema: type: object description: Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON. required: - event - data properties: event: type: string enum: - delta - heartbeat - resync data: oneOf: - $ref: '#/components/schemas/OddsHistoryStreamEvent' - $ref: '#/components/schemas/StreamHeartbeat' - $ref: '#/components/schemas/StreamResyncEvent' examples: delta: summary: Decoded delta message value: event: delta data: event_id: '3704597661' selection_key: moneyline:home resume: 1760000000000-0 points: [] heartbeat: summary: Decoded heartbeat message value: event: heartbeat data: {} resync: summary: Decoded resync message value: event: resync data: event_id: '3704597661' resume: null reason: trimmed '400': description: Invalid request parameters or body. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Missing or invalid credentials. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Credentials are valid but do not allow this resource. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Resource was not found. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': description: Rate limit exceeded. headers: Retry-After: description: Seconds to wait before retrying when supplied by the limiter. schema: type: integer minimum: 1 X-RateLimit-Limit: description: Request bucket capacity for the active limiter bucket when supplied. schema: type: integer X-RateLimit-Remaining: description: Approximate remaining requests in the active limiter bucket when supplied. schema: type: integer minimum: 0 X-RateLimit-Bucket: description: Limiter bucket name that produced the response when supplied. schema: type: string content: application/json: schema: $ref: '#/components/schemas/RateLimitResponse' '500': description: Unexpected server error. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' description: Server-Sent Events feed for line movement on one selection. This is useful for charts that should update while an event market is moving. x-rate-limit-note: Handle HTTP 429 with backoff and avoid tight polling loops. x-stream-metering: stream_units: 1 stream_hours_formula: stream_units * open_seconds / 3600 logical_bytes_metered: true quota_policy: hard_cap applies_to: authenticated API-key connections /events/{event_id}/odds/history/ws: get: operationId: websocket_events_event_id_odds_history_ws parameters: - name: event_id in: path required: true schema: type: string title: Event Id description: Canonical event or race identifier from an event list response. - name: selection_key in: query required: true schema: type: string minLength: 1 title: Selection Key description: Stable selection identifier from an odds snapshot line, used for history and line movement. - name: market_group_id in: query required: false schema: anyOf: - type: string - type: 'null' title: Market Group Id description: Optional market grouping filter for history queries. - name: bookmakers in: query required: false schema: anyOf: - type: string - type: 'null' description: Comma-separated bookmaker allow-list title: Bookmakers description: Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys. - name: price_type in: query required: false schema: type: string pattern: ^(odds|odds_no_vig|fair_odds)$ default: odds_no_vig title: Price Type description: History price type to return, for example odds. - name: price_fields in: query required: false schema: anyOf: - type: string - type: 'null' title: Price Fields description: '`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields.' - name: include_source in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Source description: When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept. - name: include_debug_ids in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Debug Ids description: When true, include internal/subgroup/opposing IDs useful for reconciliation. - name: include_unavailable in: query required: false schema: anyOf: - type: boolean - type: 'null' title: Include Unavailable description: When true, include unavailable or suspended rows where the endpoint supports them. - name: since in: query required: false schema: anyOf: - type: string - type: 'null' description: Redis stream ID title: Since description: Resume token from a previous snapshot or stream message. Pass it after reconnecting. - name: catchup in: query required: false schema: type: boolean default: true title: Catchup description: When true, return available missed stream events after `since` before waiting for new events. - name: heartbeat_sec in: query required: false schema: type: integer maximum: 120 minimum: 5 default: 15 title: Heartbeat Sec description: Heartbeat interval in seconds for stream liveness. Valid range is 5-120. - name: max_batch in: query required: false schema: type: integer maximum: 2000 minimum: 1 default: 500 title: Max Batch description: Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients. responses: '101': description: WebSocket connection established. Messages are JSON objects. content: application/json: schema: type: object description: Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON. required: - event - data properties: event: type: string enum: - delta - heartbeat - resync data: oneOf: - $ref: '#/components/schemas/OddsHistoryStreamEvent' - $ref: '#/components/schemas/StreamHeartbeat' - $ref: '#/components/schemas/StreamResyncEvent' examples: delta: summary: Delta message value: event: delta data: event_id: '3704597661' selection_key: moneyline:home resume: 1760000000000-0 points: [] heartbeat: summary: Heartbeat message value: event: heartbeat data: {} resync: summary: Resync message value: event: resync data: event_id: '3704597661' resume: null reason: trimmed '400': description: Invalid request parameters or body. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Missing or invalid credentials. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Credentials are valid but do not allow this resource. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Resource was not found. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': description: Rate limit exceeded. headers: Retry-After: description: Seconds to wait before retrying when supplied by the limiter. schema: type: integer minimum: 1 X-RateLimit-Limit: description: Request bucket capacity for the active limiter bucket when supplied. schema: type: integer X-RateLimit-Remaining: description: Approximate remaining requests in the active limiter bucket when supplied. schema: type: integer minimum: 0 X-RateLimit-Bucket: description: Limiter bucket name that produced the response when supplied. schema: type: string content: application/json: schema: $ref: '#/components/schemas/RateLimitResponse' '500': description: Unexpected server error. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '200': description: OpenAPI tooling compatibility response schema. Runtime WebSocket connections upgrade with 101. content: application/json: schema: type: object description: Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON. required: - event - data properties: event: type: string enum: - delta - heartbeat - resync data: oneOf: - $ref: '#/components/schemas/OddsHistoryStreamEvent' - $ref: '#/components/schemas/StreamHeartbeat' - $ref: '#/components/schemas/StreamResyncEvent' examples: delta: summary: Delta message value: event: delta data: event_id: '3704597661' selection_key: moneyline:home resume: 1760000000000-0 points: [] heartbeat: summary: Heartbeat message value: event: heartbeat data: {} resync: summary: Resync message value: event: resync data: event_id: '3704597661' resume: null reason: trimmed x-websocket: true x-stream-equivalent: /events/{event_id}/odds/history/stream tags: - Sports odds summary: 'Event odds history: Stream (WebSocket)' description: WebSocket feed for line movement on one selection. Messages mirror the history SSE stream payloads. security: - ApiKeyAuth: [] x-rate-limit-note: Handle HTTP 429 with backoff and avoid tight polling loops. x-stream-metering: stream_units: 1 stream_hours_formula: stream_units * open_seconds / 3600 logical_bytes_metered: true quota_policy: hard_cap applies_to: authenticated API-key connections components: schemas: StreamHeartbeat: type: object description: Heartbeat payload. All streams use it as a liveness signal; event-odds streams also include the latest event and bookmaker freshness metadata so a no-price-change capture is observable. additionalProperties: false properties: event_id: type: string nullable: true resume: type: string nullable: true as_of_ts_ms: type: integer nullable: true snapshot_capture_ts_ms: type: integer nullable: true bookmaker_as_of_ts_ms: type: object additionalProperties: type: integer oldest_bookmaker_as_of_ts_ms: type: integer nullable: true target_refresh_interval_seconds: type: integer nullable: true OddsStreamDelta: properties: op: title: Op type: string odd: $ref: '#/components/schemas/OddLine' required: - op - odd title: OddsStreamDelta type: object OddsHistoryMeta: properties: from_ts: type: string format: date-time title: From Ts to_ts: type: string format: date-time title: To Ts available_price_types: items: type: string type: array title: Available Price Types source: type: string title: Source default: unknown degraded: type: boolean title: Degraded default: false degraded_reason: anyOf: - type: string - type: 'null' title: Degraded Reason type: object required: - from_ts - to_ts - available_price_types title: OddsHistoryMeta OddsStreamEvent: properties: event_id: title: Event Id type: string resume: title: Resume type: string changes: items: $ref: '#/components/schemas/OddsStreamDelta' title: Changes type: array snapshot_id: anyOf: - type: string - type: 'null' default: null title: Snapshot Id batch_index: anyOf: - type: integer - type: 'null' default: null title: Batch Index batch_count: anyOf: - type: integer - type: 'null' default: null title: Batch Count required: - event_id - resume - changes title: OddsStreamEvent type: object RateLimitResponse: allOf: - $ref: '#/components/schemas/ErrorResponse' description: Rate-limit response. Retry after the window or reduce polling frequency. OddLine: properties: id: type: string title: Id event_id: type: string title: Event Id bookmaker: type: string title: Bookmaker bookmaker_name: anyOf: - type: string - type: 'null' title: Bookmaker Name source_type: anyOf: - type: string - type: 'null' title: Source Type exchange: anyOf: - type: string - type: 'null' title: Exchange exchange_market_id: anyOf: - type: string - type: 'null' title: Exchange Market Id exchange_selection_id: anyOf: - type: string - type: 'null' title: Exchange Selection Id selection_key: anyOf: - type: string - type: 'null' title: Selection Key market_group_id: anyOf: - type: string - type: 'null' title: Market Group Id market_key: type: string title: Market Key type: anyOf: - type: string - type: 'null' title: Type bet_type: anyOf: - type: string - type: 'null' title: Bet Type period: anyOf: - type: integer - type: string title: Period period_str: anyOf: - type: string - type: 'null' title: Period Str metric: anyOf: - type: string - type: 'null' title: Metric line: anyOf: - type: string - type: 'null' title: Line side: anyOf: - type: string - type: 'null' title: Side player_name: anyOf: - type: string - type: 'null' title: Player Name selection_name: anyOf: - type: string - type: 'null' title: Selection Name market_contract: anyOf: - additionalProperties: true type: object - type: 'null' title: Market Contract description: 'Categorical market contract: version, source_rules, scope, and source_market. Part of market identity; different source contracts are not assumed settlement-equivalent.' selection_parameters: anyOf: - additionalProperties: true type: object - type: 'null' title: Selection Parameters description: 'Structured categorical outcome: predicate, methods, rounds, includes_decision and includes_draw as applicable. Finishing rounds are outcomes within full time, not periods.' number_of_outcomes: anyOf: - type: integer - type: 'null' title: Number Of Outcomes odds: anyOf: - type: number - type: 'null' title: Odds odds_no_vig: anyOf: - type: number - type: 'null' title: Odds No Vig odds_no_vig_multiplicative_method: anyOf: - type: number - type: 'null' title: Odds No Vig Multiplicative Method odds_no_vig_additive_method: anyOf: - type: number - type: 'null' title: Odds No Vig Additive Method odds_no_vig_power_method: anyOf: - type: number - type: 'null' title: Odds No Vig Power Method odds_no_vig_shin_method: anyOf: - type: number - type: 'null' title: Odds No Vig Shin Method fair_odds: anyOf: - type: number - type: 'null' title: Fair Odds main_line_eligible: type: boolean title: Main Line Eligible default: true best_back_price: anyOf: - type: number - type: 'null' title: Best Back Price best_back_size: anyOf: - type: number - type: 'null' title: Best Back Size best_lay_price: anyOf: - type: number - type: 'null' title: Best Lay Price best_lay_size: anyOf: - type: number - type: 'null' title: Best Lay Size last_traded_price: anyOf: - type: number - type: 'null' title: Last Traded Price traded_volume: anyOf: - type: number - type: 'null' title: Traded Volume total_matched: anyOf: - type: number - type: 'null' title: Total Matched liquidity_source: anyOf: - type: string - type: 'null' title: Liquidity Source liquidity: anyOf: - type: number - type: 'null' title: Liquidity back_liquidity: anyOf: - type: number - type: 'null' title: Back Liquidity lay_liquidity: anyOf: - type: number - type: 'null' title: Lay Liquidity is_available: type: boolean title: Is Available default: true type: object required: - id - event_id - bookmaker - market_key - period title: OddLine OddsHistorySeries: properties: bookmaker_name: type: string title: Bookmaker Name points: items: $ref: '#/components/schemas/OddsHistoryPoint' type: array title: Points type: object required: - bookmaker_name - points title: OddsHistorySeries OddsHistoryPoint: properties: tick_ts: type: string format: date-time title: Tick Ts is_available: type: boolean title: Is Available value: anyOf: - type: number - type: 'null' title: Value odds: anyOf: - type: number - type: 'null' title: Odds odds_no_vig: anyOf: - type: number - type: 'null' title: Odds No Vig fair_odds: anyOf: - type: number - type: 'null' title: Fair Odds type: object required: - tick_ts - is_available title: OddsHistoryPoint ErrorResponse: type: object required: - detail properties: detail: description: Human-readable error detail. FastAPI may return a string or a validation-error object. oneOf: - type: string - type: object - type: array items: type: object code: type: string description: Optional stable error code when provided by the endpoint. request_id: type: string description: Optional request identifier for support/debugging. OddsHistoryStreamEvent: type: object required: - event_id - selection_key - resume - points properties: event_id: type: string selection_key: type: string resume: type: string points: type: array items: $ref: '#/components/schemas/OddsHistoryPoint' OddsSnapshotResponse: properties: event_id: type: string title: Event Id as_of_ts_ms: anyOf: - type: integer - type: 'null' title: As Of Ts Ms description: Time the API accepted the latest successful snapshot or authoritative bookmaker subset. snapshot_capture_ts_ms: anyOf: - type: integer - type: 'null' title: Snapshot Capture Ts Ms description: Time the snapshot producer completed the latest published event capture. bookmaker_as_of_ts_ms: additionalProperties: type: integer type: object title: Bookmaker As Of Ts Ms description: Last API acceptance time for each bookmaker included in this response. oldest_bookmaker_as_of_ts_ms: anyOf: - type: integer - type: 'null' title: Oldest Bookmaker As Of Ts Ms description: Oldest acceptance time among bookmaker_as_of_ts_ms values. target_refresh_interval_seconds: anyOf: - type: integer - type: 'null' title: Target Refresh Interval Seconds description: Current scheduler target for this event; source health and runtime load can add delay. ttl_seconds: anyOf: - type: integer - type: 'null' title: Ttl Seconds items: items: $ref: '#/components/schemas/OddLine' type: array title: Items next_cursor: anyOf: - type: string - type: 'null' title: Next Cursor complete: type: boolean title: Complete description: True when there are no more bookmaker-complete pages for this request. default: true bookmakers_included: items: type: string type: array title: Bookmakers Included description: Bookmakers whose complete matching line sets are included in this response. bookmaker_counts: additionalProperties: type: integer type: object title: Bookmaker Counts description: Number of returned matching lines for each included bookmaker. resume: type: string title: Resume description: Opaque stream resume token; pass it as 'since' to /stream. type: object required: - event_id - items - resume title: OddsSnapshotResponse StreamResyncEvent: type: object description: Sent when a resume token is no longer available and the client should reload the snapshot. required: - reason properties: event_id: type: string nullable: true selection_key: type: string nullable: true resume: type: string nullable: true reason: type: string example: trimmed snapshot_id: type: string nullable: true OddsHistoryResponse: properties: event_id: type: string title: Event Id selection_key: type: string title: Selection Key price_type: type: string title: Price Type series: items: $ref: '#/components/schemas/OddsHistorySeries' type: array title: Series meta: $ref: '#/components/schemas/OddsHistoryMeta' type: object required: - event_id - selection_key - price_type - series - meta title: OddsHistoryResponse securitySchemes: ApiKeyAuth: type: apiKey in: header name: X-API-Key description: Send your API key in this header on every request.