# AI & LLM Integration - Documentation Export Source: https://docs.oddspapi.io/ai Download OddsPapi documentation for AI tools, LLMs, and offline use. Machine-readable exports in TXT and OpenAPI JSON formats for ChatGPT, Claude, and other AI assistants. ## Download * **Docs index:** [`/llms.txt`](/llms.txt) * **Full docs bundle:** [`/llms-full.txt`](/llms-full.txt) * **OpenAPI (REST reference):** [`/api-reference/openapi.json`](/api-reference/openapi.json) ## Recommended usage If your AI tool supports fetching URLs, give it: * `/llms-full.txt` (best for β€œread everything once”) * `/api-reference/openapi.json` (best for endpoint + schema accuracy) If your tool only supports copy/paste, open `/llms-full.txt` in the browser and paste the contents. ## Notes * `llms-full.txt` follows the same ordering as the site navigation. * WebSocket channels are documented in MDX; REST endpoints are in OpenAPI. *** ## πŸ’¬ Ask an AI Assistant Want to explore or ask questions about this page using your favorite AI? Click one of the links below β€” each one opens this page in the selected tool with a pre-filled prompt: * [Ask ChatGPT](https://chatgpt.com/?prompt=Read+from+https%3A%2F%2Fdocs.oddspapi.io%2Fllms-full.txt+and+help+me+with+this+API.) * [Ask Claude](https://claude.ai/?prompt=Please+read+https%3A%2F%2Fdocs.oddspapi.io%2Fllms-full.txt+and+help+me+use+this+API.) * [Ask Perplexity](https://www.perplexity.ai/search?q=Read+from+https%3A%2F%2Fdocs.oddspapi.io%2Fllms-full.txt) * [Ask Gemini](https://gemini.google.com/app?query=Read+from+https%3A%2F%2Fdocs.oddspapi.io%2Fllms-full.txt+and+help+me+use+this+API.) # API Authentication - How to Authenticate Requests Source: https://docs.oddspapi.io/api-reference/authentication Learn how to authenticate OddsPapi API requests using your API key. Simple query parameter authentication for all HTTP endpoints. All HTTP endpoints require an `apiKey`. You will only receive: 1. what you are requesting, filtered by 2. what you have access for ## How to authenticate Pass your key as a query parameter: ```bash theme={null} curl 'https://v5.oddspapi.io/en/bookmakers?apiKey=YOUR_KEY' ``` # Get Bookmakers Source: https://docs.oddspapi.io/api-reference/common/get-bookmakers /api-reference/openapi.json get /bookmakers List bookmakers. Returns the bookmaker catalog available to the provided apiKey. Lookup mode: `bookmakers` (optional filter list) + `playerProps` (optional capability filter). # Get Currencies Source: https://docs.oddspapi.io/api-reference/common/get-currencies /api-reference/openapi.json get /currencies List currencies and conversion values. Lookup mode: `currency` (optional filter). # Get Markets Source: https://docs.oddspapi.io/api-reference/common/get-markets /api-reference/openapi.json get /markets List markets. Lookup mode: `marketIds` OR `sportId` OR `outcomeIds`. # Get Participants Source: https://docs.oddspapi.io/api-reference/common/get-participants /api-reference/openapi.json get /participants List participants. Lookup mode: `participantIds` OR `sportId` OR `playerId`. # Get Players Source: https://docs.oddspapi.io/api-reference/common/get-players /api-reference/openapi.json get /players List players. Lookup mode: `playerIds` OR `participantId` OR `sportId`. # Get Seasons Source: https://docs.oddspapi.io/api-reference/common/get-seasons /api-reference/openapi.json get /seasons List seasons. Lookup mode: `seasonIds` OR `tournamentId`. # Get Sports Source: https://docs.oddspapi.io/api-reference/common/get-sports /api-reference/openapi.json get /sports List sports. Lookup mode: `sportIds` (optional filter list). # Get Tournaments Source: https://docs.oddspapi.io/api-reference/common/get-tournaments /api-reference/openapi.json get /tournaments List tournaments. Lookup mode: `sportId` OR `tournamentIds`. Default: if neither is provided, the API uses `sportId=11`. # Get Venues Source: https://docs.oddspapi.io/api-reference/common/get-venues /api-reference/openapi.json get /venues List venues by explicit IDs. Name fields are returned in the requested language when available. # Core Concepts - IDs, Data Models & Best Practices Source: https://docs.oddspapi.io/api-reference/concepts Understand OddsPapi data models: fixture IDs, market IDs, odds identifiers, entity relationships, timestamps, and implementation best practices for arbitrage detection. This page explains the core concepts used throughout the Odds API: how IDs are constructed, how entities relate to each other, how timestamps are used, and how to design reliable client-side storage. *** ## Language prefix All endpoints are prefixed with a language code: * `/en/...` * `/de/...` * `/fr/...` Translated fields (for example names) follow the prefix language when available. Identifiers (`sportId`, `fixtureId`, etc.) are language-independent. *** ## Sports, tournaments, seasons ### sportId * Integer. * The **first two digits** embedded into several downstream IDs. * Discover via `/sports`. ### tournamentId * Integer. * Belongs to exactly one sport. * Discover via `/tournaments`. ### seasonId * Integer. * Belongs to exactly one tournament. * Discover via `/seasons`. > See the full [Sports Coverage](/coverage) table for every `sportId` and whether it's available as fixtures, futures, or both. *** ## Markets and outcomes **TL;DR** β€” Every selection follows a fixed hierarchy: `marketType β†’ period β†’ handicap β†’ side`, all baked into the `outcomeId` β€” so the same bet always resolves to the same `marketId` / `outcomeId`, addressed by coordinates rather than bookmaker name strings. The one part *not* in `outcomeId` is the player, carried by `playerId`. All outcomes of a market share its `marketId`, which equals the market's first `outcomeId`. ### How markets decompose (the deterministic hierarchy) Every `outcomeId` comes from a single, deterministic decomposition β€” OddsPapi splits each market into a fixed hierarchy: ``` marketType β†’ period β†’ handicap (line value / specifier) β†’ side (outcome) [+ optional player] ``` So "total goals over 2.5, full time" always lands on the same `marketId` / `outcomeId` across bookmakers and fixtures. | Level | Field | Meaning | | ----- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | `marketType` | The kind of market (`1x2`, `totals`, `spreads`, `spreads-european`, `bothteamsscore`, `drawnobet`, `oddeven`, …). | | 2 | `period` | The segment the market applies to (`fulltime`, `p1`, `p2`). | | 3 | `handicap` | The line value / specifier (e.g. `2.5`, `-0.25`). **Each distinct line value is its own `marketId`.** `0.0` for lineless markets like `1x2`. | | 4 | `outcomeName` | The **side** of the market (`Over` / `Under`, `1` / `X` / `2`, `Yes` / `No`, …). | > **Key rule:** because every line value gets its **own** `marketId`, "Over 2.5" and "Over 3.5" are *different* markets β€” not two outcomes of one market. Within a single `marketId`, the outcomes are only the **sides** of that exact line (e.g. `Over 2.5` + `Under 2.5`). A representative slice of the real soccer market map. Each `marketId` equals its first `outcomeId`, and each line value is its own market: | marketId | marketName | marketType | period | handicap | outcomeId | outcomeName | | -------: | -------------------- | ------------------ | -------- | -------: | --------: | ----------- | | 101 | Full Time Result | `1x2` | fulltime | 0.0 | 101 | 1 | | 101 | Full Time Result | `1x2` | fulltime | 0.0 | 102 | X | | 101 | Full Time Result | `1x2` | fulltime | 0.0 | 103 | 2 | | 104 | Both Teams To Score | `bothteamsscore` | fulltime | 0.0 | 104 | Yes | | 104 | Both Teams To Score | `bothteamsscore` | fulltime | 0.0 | 105 | No | | 106 | Over Under Full Time | `totals` | fulltime | 0.5 | 106 | Over | | 106 | Over Under Full Time | `totals` | fulltime | 0.5 | 107 | Under | | 1010 | Over Under Full Time | `totals` | fulltime | 2.5 | 1010 | Over | | 1010 | Over Under Full Time | `totals` | fulltime | 2.5 | 1011 | Under | | 1068 | Asian Handicap | `spreads` | fulltime | -0.5 | 1068 | 1 | | 1068 | Asian Handicap | `spreads` | fulltime | -0.5 | 1069 | 2 | | 10122 | European Handicap | `spreads-european` | fulltime | 6.0 | 10122 | 1 | | 10122 | European Handicap | `spreads-european` | fulltime | 6.0 | 10123 | X | | 10122 | European Handicap | `spreads-european` | fulltime | 6.0 | 10124 | 2 | | 10208 | First Half Result | `1x2` | p1 | 0.0 | 10208 | 1 | | 10208 | First Half Result | `1x2` | p1 | 0.0 | 10209 | X | | 10208 | First Half Result | `1x2` | p1 | 0.0 | 10210 | 2 | | 10214 | Draw No Bet | `drawnobet` | fulltime | 0.0 | 10214 | 1 | | 10214 | Draw No Bet | `drawnobet` | fulltime | 0.0 | 10215 | 2 | | 10222 | Odd Even Full Time | `oddeven` | fulltime | 0.0 | 10222 | Odd | | 10222 | Odd Even Full Time | `oddeven` | fulltime | 0.0 | 10223 | Even | | 10224 | Over Under Team 1 | `teamtotals-team1` | fulltime | 0.5 | 10224 | Over | | 10224 | Over Under Team 1 | `teamtotals-team1` | fulltime | 0.5 | 10225 | Under | Reading the first row: `marketType=1x2`, `period=fulltime`, `handicap=0.0`, `outcomeName=1` β€” the home side of the full-time match-result market. ### Relationship between `marketId` and `outcomeId` Markets and outcomes are tightly coupled by design: * A **market** represents a complete betting market (for example moneyline, 1x2, totals). * A **market contains multiple outcomes**. * **All outcomes that belong to the same market share the same `marketId`.** * **The first `outcomeId` of a market is always equal to the `marketId`.** This makes it possible to determine which outcomes belong to which market **without additional metadata**. ### ID structure Both `marketId` and `outcomeId` are integers constructed as: ``` {sportId (2 digits)} + {incrementing number} ``` Examples: * `11xxxx` β†’ basketball market or outcome * `14xxxx` β†’ American football market or outcome This design provides: * Unlimited markets and outcomes per sport * Fast grouping by sport * Immediate visibility of market relationships ### Why `marketId` matters (arbitrage & modeling) All outcomes under a single `marketId` together represent a **complete probability space**. This makes `marketId` especially useful for: * Arbitrage detection * Overround / margin calculations * Probability normalization * Market completeness checks **Recommendation:** If you perform arbitrage or pricing logic, always group odds by `marketId`. *** ## Participants and players ### participantId * Integer. * Represents teams or competitors in fixtures. * Discover via `/participants`. ### playerId * Integer. * Used for player proposition markets. * `playerId = 0` typically represents a non-player market. * Discover via `/players`. *** ## Fixture IDs ### fixtureId structure `fixtureId` is a **string** that encodes multiple pieces of information: ``` {providerSlug}{sportId}{tournamentId}{providerFixtureId} ``` Conceptual example: ``` id1100013262926199 ``` Where: * `id` β†’ provider identifier / short slug * `11` β†’ sportId (2 digits) * `000132` β†’ tournamentId (6 digits) * `62926199` β†’ provider’s native fixture ID From the `fixtureId` alone you can infer the sport, tournament, and upstream provider β€” handy for logging and cross-system correlation. *** ## Future IDs ### futureId structure `futureId` follows a similar principle: ``` {providerSlug}{sportId}{seasonId}{marketId} ``` This encodes provider, sport, season, and market β€” globally unique and self-describing, like `fixtureId`. *** ## Odds identifiers **TL;DR** β€” A single price is uniquely keyed by `{fixtureId}:{bookmaker}:{outcomeId}:{playerId}` (fixtures) or `{futureId}:{bookmaker}:{participantId}` (futures). Use these `oddsId` strings as **primary keys** in your storage for clean dedup, updates, and reconciliation. ### Fixture odds keys For fixtures, a single price is uniquely identified by: ``` {fixtureId}:{bookmaker}:{outcomeId}:{playerId} ``` Example: ``` id1400003160574217:bet365:141:0 ``` This combination uniquely defines **one price**. Drop the `bookmaker` and you have the **selection** itself β€” `{fixtureId}:{outcomeId}:{playerId}` β€” the bookmaker-independent key for grading / settlement, since a selection wins or loses the same way everywhere. No `marketId` is needed; the `outcomeId` already encodes the market. ### Future odds IDs For futures, odds are uniquely identified by: ``` {futureId}:{bookmaker}:{participantId} ``` *** ## Timestamps (seconds vs milliseconds) This API intentionally uses **both epoch seconds and epoch milliseconds**, depending on context. ### Epoch seconds (UTC) Used for scheduled or coarse-grained time values: * `startTime` * `startTimeFrom` * `startTimeTo` ### Epoch milliseconds (UTC) Used for high-frequency price updates: * `changedAt` * Odds `since` filters for fixtures * Futures odds `createdAt` ### Recommendation * Store timestamps as integers. * Do not assume milliseconds where seconds are documented (or vice versa). * Normalize internally only if needed. *** ## Operating tips ### Server Location Matters To achieve the lowest latency for realtime updates: * The **fastest delivery regions** are Central Europe (recommended) and US East. * For best performance, deploy your backend **in the same datacenters we use**, such as: * [Netcup](https://www.netcup.com/en/?ref=294017) * [Hetzner](https://www.hetzner.com/cloud) * If you're building **prediction markets or models sensitive to latency**, consider: * Deploying in **Austria (AT)** via Netcup, or * Using **AWS eu-west-1** (Ireland) > ⚑ Servers colocated near our streaming infrastructure receive updates faster and with fewer hops. ### Snapshot + realtime pattern A reliable integration pattern: Pull the current state (fixtures, odds, or futures) over REST. Stream realtime updates on top of the snapshot. If the WebSocket signals `snapshot_required`, re-fetch the HTTP snapshot and resume the realtime stream. ### Efficient backfills * Use `since` parameters where available * Avoid full historical fetches unless required * Store odds keyed by their odds identifiers for deduplication ### Rate limits See [Rate Limits](/api-reference/rate-limits) for headers, per-endpoint limits, and 429 backoff handling. *** ## Historical odds and CLV The realtime WebSocket `odds` and `oddsFutures` channels deliver **latest state** β€” optimized for low-latency trading, they coalesce or drop intermediate updates under load and are not a tick ledger. > Use the live stream to **trade**, and the REST history endpoints to **measure** β€” CLV models, fill auditing, and backtests. When you need the **full price movement** of an outcome, or its **opening vs closing line**, use the REST history endpoints. They share the same `oddsId` format (`{fixtureId}:{bookmaker}:{outcomeId}:{playerId}`) as the live `odds` channel, so you can join realtime fills directly to their historical and closing-line records. | Purpose | Fixtures | Futures | | --------------------------------- | ------------------------------- | ------------------------------ | | Full price timeline | `GET /fixtures/odds/historical` | `GET /futures/odds/historical` | | Opening vs closing line (OLV/CLV) | `GET /fixtures/odds/clv` | `GET /futures/odds/clv` | **Trading use cases:** * **CLV modelling** β€” compare your fill price against the closing line to measure edge. * **Fill auditing** β€” reconcile traded prices with the recorded timeline to detect slippage. * **Backtesting** β€” replay the full historical timeline to test strategies against real line movement. See the **API β†’ Reference** section for full request parameters, response schemas, and an interactive playground for each of these endpoints. *** ## Glossary Quick definitions of the core terms used throughout the API. **Identifiers & data model** * **sportId** β€” Numeric sport identifier; the first two digits of downstream `marketId`, `outcomeId`, and `fixtureId`. * **fixtureId** β€” String key for a single match/event, encoding provider, sport, tournament, and provider fixture ID. * **futureId** β€” String key for a futures / outright market, encoding provider, sport, season, and market. * **marketType** β€” The kind of market (`1x2`, `totals`, `spreads`, `bothteamsscore`, …). * **period** β€” The segment a market applies to (`fulltime`, `p1`, `p2`). * **handicap** β€” The line value / specifier of a market; each distinct line value is its own `marketId`. * **marketId** β€” Integer grouping all outcomes of one market; equals the market's first `outcomeId`. * **outcomeId** β€” Integer identifying one fully-decomposed selection: `marketType` + `period` + `handicap` (line) + side. It encodes everything except the player β€” that's what `playerId` adds. * **playerId** β€” Ties an outcome to a player for player markets; `playerId = 0` is a non-player market. * **participantId** β€” Integer identifying a team or competitor in a fixture. * **oddsId** β€” Composite price key. Fixtures: `{fixtureId}:{bookmaker}:{outcomeId}:{playerId}`. Futures: `{futureId}:{bookmaker}:{participantId}`. **Prices & trading** * **OLV** β€” Opening line value: the outcome's first recorded price. * **CLV** β€” Closing line value: the outcome's last price before settlement; the reference for grading execution. * **staleOdds** β€” Flag indicating a bookmaker's connectivity is degraded, so its odds may not be current. * **settlement** β€” Post-event endpoints returning per-outcome results (won / lost), final scores, and margins. **Streaming & delivery** * **serverEpoch + entryId** β€” The per-channel cursor; a change in `serverEpoch` means the client must re-snapshot. * **resume / replay** β€” Reconnect and replay missed messages within `resumeWindowMs` (default `60000`). * **snapshot\_required** β€” Signal that the cursor is outside the resume window and a fresh REST snapshot is needed. # API Error Codes - HTTP Status Codes & Error Handling Source: https://docs.oddspapi.io/api-reference/errors Complete guide to OddsPapi API error responses. HTTP status codes, error formats, rate limit headers, and troubleshooting common error causes. ## Error Format Most error responses return a JSON object like: ```json theme={null} { "error": 401, "message": "invalid apiKey", "code": "invalid_api_key" } ``` | Field | Type | Description | | --------- | ------- | ---------------------------- | | `error` | integer | HTTP status code | | `message` | string | Human-readable description | | `code` | string | Machine-readable reason code | *** ## Rate Limit Headers All endpoints protected by the rate limiter return these headers: | Header | Description | | ----------------------- | ---------------------------------------- | | `X-RateLimit-Limit` | Requests allowed per window (per apiKey) | | `X-RateLimit-Remaining` | Requests left in the current window | | `X-RateLimit-Reset` | Unix timestamp when the window resets | If you hit the limit (`429`), you also receive: | Header | Description | | ------------- | ------------------------------- | | `Retry-After` | Seconds to wait before retrying | *** ## Common Error Responses ### 400 β€” Bad Request ```json theme={null} { "error": 400, "message": "Invalid filters.", "code": "invalid_filters" } ``` Used for syntactically valid requests with invalid parameters. *** ### 401 β€” Unauthorized **Missing or invalid `apiKey`.** ```json theme={null} { "error": 401, "message": "missing apiKey", "code": "missing_api_key" } ``` ```json theme={null} { "error": 401, "message": "invalid apiKey", "code": "invalid_api_key" } ``` *** ### 403 β€” Forbidden **Valid API key, but no access to this channel or feature.** ```json theme={null} { "error": 403, "message": "Access denied: apiKey is not allowed to access this endpoint.", "code": "channel_not_allowed" } ``` *** ### 422 β€” Validation Error Usually returned by FastAPI’s internal validation. ```json theme={null} { "error": 422, "message": "Validation error", "code": "validation_error", "details": [ { "loc": ["query", "fixtureId"], "msg": "Field required", "type": "missing" } ] } ``` *** ### 429 β€” Rate Limit Exceeded Triggered when request volume exceeds limits per apiKey. ```json theme={null} { "error": 429, "message": "rate limit exceeded", "code": "rate_limited", "limit": 30, "windowSec": 1, "retryAfterSec": 1, "endpoint": "/fixtures/odds", "method": "GET" } ``` Headers: * `Retry-After: 1` * `X-RateLimit-Limit: 30` * `X-RateLimit-Remaining: 0` * `X-RateLimit-Reset: 1700000000` *** ### 503 β€” Service Unavailable If internal systems (e.g. rate limiter) are unavailable: ```json theme={null} { "error": 503, "message": "rate limiter unavailable", "code": "rate_limiter_error" } ``` ## Notes * `code` is stable and suitable for programmatic handling. * `message` may change (but is designed to help developers). * Most endpoints return `429` if rate-limited β€” check your headers. * FastAPI validation errors are always returned as `422`. *** # Fixtures Filtered Source: https://docs.oddspapi.io/api-reference/fixtures/fixtures-filtered /api-reference/openapi.json get /fixtures List fixtures. Lookup mode: `fixtureIds` OR filters (`sportId`, `tournamentId`, `statusId`, optional time range). If `bookmakers` is present, bookmaker meta is included and results may be filtered by mapping availability. # Fixtures Live Source: https://docs.oddspapi.io/api-reference/fixtures/fixtures-live /api-reference/openapi.json get /fixtures/live List live fixtures. Lookup mode: optional filters (`bookmakers`, `sportId`, `tournamentId`). # Fixtures Today Source: https://docs.oddspapi.io/api-reference/fixtures/fixtures-today /api-reference/openapi.json get /fixtures/today List today's fixtures. Lookup mode: optional filters (`bookmakers`, `sportId`, `tournamentId`). # Fixture Odds Source: https://docs.oddspapi.io/api-reference/fixturesodds/fixture-odds /api-reference/openapi.json get /fixtures/odds Get current odds for a fixture. Lookup mode: required `fixtureId` + optional `bookmakers`, optional filtering flags. If `since` is provided, returns odds updates with `changedAt >= since`. # Fixture Odds CLV Source: https://docs.oddspapi.io/api-reference/fixturesodds/fixture-odds-clv /api-reference/openapi.json get /fixtures/odds/clv Get opening vs closing line values (OLV/CLV) for a fixture. Lookup mode: required `fixtureId` + optional `bookmakers` OR optional `oddsIds`. # Fixture Odds Historical Source: https://docs.oddspapi.io/api-reference/fixturesodds/fixture-odds-historical /api-reference/openapi.json get /fixtures/odds/historical Get historical odds timeline for a fixture. Lookup mode: required `fixtureId` + (`bookmaker` required when `oddsIds` is not provided) OR `oddsIds`. # Fixtures Odds Main Source: https://docs.oddspapi.io/api-reference/fixturesodds/fixtures-odds-main /api-reference/openapi.json get /fixtures/odds/main Get current main odds for multiple fixtures. Lookup mode: provide exactly one of: - `tournamentId` (recommended) - `fixtureIds` (fast path) If `since` is provided, returns odds updates with `changedAt >= since`. # Futures Filtered Source: https://docs.oddspapi.io/api-reference/futures/futures-filtered /api-reference/openapi.json get /futures List futures. Lookup mode: `futureIds` OR `sportId` OR `tournamentId` (at least one). # Futures Live Source: https://docs.oddspapi.io/api-reference/futures/futures-live /api-reference/openapi.json get /futures/live List live futures. Lookup mode: optional `sportId` / `tournamentId` filters. # Future Odds Source: https://docs.oddspapi.io/api-reference/futuresodds/future-odds /api-reference/openapi.json get /futures/odds Get latest odds for a future. Lookup mode: required `futureId` + optional filters (`bookmakers`, `since`, `mainLines`, `includeFuture`). # Future Odds CLV Source: https://docs.oddspapi.io/api-reference/futuresodds/future-odds-clv /api-reference/openapi.json get /futures/odds/clv Get OLV and CLV for a future’s odds. Lookup mode: required `futureId` + optional `bookmakers` OR optional `oddsIds`. # Future Odds Historical Source: https://docs.oddspapi.io/api-reference/futuresodds/future-odds-historical /api-reference/openapi.json get /futures/odds/historical Get historical odds changes for a future. Lookup mode: required `futureId` + (`bookmakers` required when `oddsIds` is not provided) OR `oddsIds`. # Fixture Mapping Source: https://docs.oddspapi.io/api-reference/mapping/fixture-mapping /api-reference/openapi.json get /fixtures/mapping List fixture mappings for external bookmakers. Lookup mode: `bookmaker` (optional) + `fixtureIds` OR `bookmakerFixtureIds`. # Future Mapping Source: https://docs.oddspapi.io/api-reference/mapping/future-mapping /api-reference/openapi.json get /futures/mapping Map internal future IDs to bookmaker/provider future IDs. Lookup mode: required `bookmaker` + (`futureIds` OR `bookmakerFutureIds`). # Media Bookmaker Source: https://docs.oddspapi.io/api-reference/media/media-bookmaker /api-reference/openapi.json get /media/bookmakers/{slug} Get bookmaker logo. Returns an HTTP redirect to the image asset. Response: 302 with `Location` header (final resource typically WebP). # Media Category Source: https://docs.oddspapi.io/api-reference/media/media-category /api-reference/openapi.json get /media/categories/{category} Get category icon. Returns an HTTP redirect to the image asset. Response: 302 with `Location` header (final resource typically SVG). # Media Participant Source: https://docs.oddspapi.io/api-reference/media/media-participant /api-reference/openapi.json get /media/participants/{participantId} Get participant image. Returns an HTTP redirect to the image asset. Response: 302 with `Location` header (final resource typically PNG). # Media Tournament Source: https://docs.oddspapi.io/api-reference/media/media-tournament /api-reference/openapi.json get /media/tournaments/{tournamentId} Get tournament image. Returns an HTTP redirect to the image asset. Response: 302 with `Location` header (final resource typically PNG). # REST API Overview - HTTP Endpoints for Sports Data Source: https://docs.oddspapi.io/api-reference/overview OddsPapi REST API reference. HTTP endpoints for sports betting data snapshots, metadata retrieval, historical odds, and recovery workflows. Supports 8 languages. The HTTP API complements WebSockets by providing: * **Initial snapshots** (bootstrap your local state) * **Recovery** after `snapshot_required` * **Metadata** (sports, tournaments, bookmakers) * **Backfills** (historical odds / CLV) when replay is not possible If you only need realtime updates, WebSockets may be enough. ## Base URL & languages All endpoints are prefixed with a language code: * `https://v5.oddspapi.io/{lang}` Examples: * `https://v5.oddspapi.io/en/bookmakers` * `https://v5.oddspapi.io/de/sports` Available languages are: `en`, `es`, `fr`, `pt`, `de`, `it`, `ru`, `zh`. For simplicity, the documentation is written in English. ## Rate limits This API enforces request limits per `apiKey` per endpoint. See **Rate Limits** for the authoritative limits and endpoint groups: * `api-reference/rate-limits` *** ## πŸ’¬ Ask an AI Assistant Want to explore or ask questions about this page using your favorite AI? Click one of the links below β€” each one opens this page in the selected tool with a pre-filled prompt: * [Ask ChatGPT](https://chatgpt.com/?prompt=Read+from+https%3A%2F%2Fdocs.oddspapi.io%2Fllms-full.txt+and+help+me+with+this+API.) * [Ask Claude](https://claude.ai/?prompt=Please+read+https%3A%2F%2Fdocs.oddspapi.io%2Fllms-full.txt+and+help+me+use+this+API.) * [Ask Perplexity](https://www.perplexity.ai/search?q=Read+from+https%3A%2F%2Fdocs.oddspapi.io%2Fllms-full.txt) * [Ask Gemini](https://gemini.google.com/app?query=Read+from+https%3A%2F%2Fdocs.oddspapi.io%2Fllms-full.txt+and+help+me+use+this+API.) # API Rate Limits - Request Quotas & Throttling Source: https://docs.oddspapi.io/api-reference/rate-limits OddsPapi API rate limits and quotas. Request limits per endpoint, rate limit headers, and best practices for handling 429 responses. Rate limits are counted **per `apiKey`** and by endpoint. ## Limits ### Odds endpoints (high-frequency) These endpoints allow higher throughput: * `GET /fixtures/odds` * `GET /fixtures/odds/main` * `GET /futures/odds` **Limit:** **10 requests / second** ### All other endpoints Everything else (metadata, fixtures list, mapping, settlements, historical, media redirects): **Limit:** **200 requests / minute** ## Rate limit headers Every response includes: * `X-RateLimit-Limit` * `X-RateLimit-Remaining` * `X-RateLimit-Reset` When limited (429), responses also include: * `Retry-After` ## What happens when you exceed limits You’ll receive: * **429** with `code: "rate_limited"` Example: ```json theme={null} { "error": 429, "message": "rate limit exceeded", "code": "rate_limited", "retryAfterSec": 1 } ``` # Reliability & Operations - Uptime, Status & Incident Response Source: https://docs.oddspapi.io/api-reference/reliability How OddsPapi delivers reliable realtime data for trading: redundant architecture, live status page, recovery design, support response, and incident handling. OddsPapi is built for teams that trade on the feed, so reliability is a first-class concern. This page explains how the platform is designed to stay available, how to monitor it, and how to reach us when something goes wrong. Specific uptime targets, support response times, and remedies are defined per agreement for B2B customers. Contact [contact@oddspapi.io](mailto:contact@oddspapi.io) to discuss SLA terms for your use case. *** ## Live status & incident history Real-time platform status, ongoing incidents, and historical uptime are published on our status page: Live availability, incident reports, and uptime history β€” subscribe for updates. Subscribe on the status page to be notified of incidents and scheduled maintenance. *** ## Architecture for availability The platform is designed so that no single failure should interrupt your data flow: * **WebSocket-first delivery** β€” a persistent gateway streams updates with minimal latency, avoiding the overhead and gaps of REST polling. * **REST + WebSocket redundancy** β€” REST snapshots and the realtime stream are independent paths to the same data. If the stream drops, REST keeps your state current. * **Resume & replay** β€” after a brief disconnect, clients resume from their last cursor and replay missed updates without a full refresh. See [Resume & Replay](/websocket/resume-replay). * **Deterministic recovery** β€” when replay isn't possible, the gateway emits `snapshot_required` so clients rebuild state from a fast REST snapshot. Recovery is an explicit, designed path β€” not an error state. *** ## Designed for trading-grade failover Recovery is bounded and predictable rather than best-effort: * The gateway buffers a replay window (`resumeWindowMs`) so short interruptions resume with **no data loss**. * If your cursor falls outside that window, a single REST snapshot restores full state in one call. * Bookmaker connectivity is surfaced explicitly via the `staleOdds` flag, so you always know when a price may no longer be current. This means worst-case recovery is a **fast, well-defined REST snapshot**, not an open-ended outage. See [Resume & Replay](/websocket/resume-replay) for the full recovery flow. *** ## Monitoring & operational responsibility OddsPapi gives you the signals to operate safely; your client should act on them: * Track the `staleOdds` flag per bookmaker before trading on a price. * Handle `snapshot_required` and reconnect logic (see the [Resume & Replay](/websocket/resume-replay) template). * Respect [rate limits](/api-reference/rate-limits) and [WebSocket connection limits](/websocket/overview); contact support to raise limits for distributed systems. * Watch the status page for platform-level incidents. *** ## Support & incident response | Channel | Use for | | ------------------------------------------------- | ----------------------------------------------------- | | [support@oddspapi.io](mailto:support@oddspapi.io) | Technical support, integration help, incident reports | | [contact@oddspapi.io](mailto:contact@oddspapi.io) | API access, SLA terms, raising limits | | [Status page](https://oddspapi-v5.instatus.com) | Live availability and incident updates | When reporting an issue, include your `apiKey` group, affected channels or endpoints, timestamps (UTC), and any `serverEpoch` / `entryId` values β€” these let us trace your session quickly. # Fixture Settlement Source: https://docs.oddspapi.io/api-reference/settlement/fixture-settlement /api-reference/openapi.json get /fixtures/settlement Get settlements for a fixture. Lookup mode: required `fixtureId` + optional `outcomeId`, optional `playerId`. # Future Settlement Source: https://docs.oddspapi.io/api-reference/settlement/future-settlement /api-reference/openapi.json get /futures/settlement Get settlements for a future. Currently not available. # API Changelog - Updates, Features & Breaking Changes Source: https://docs.oddspapi.io/changelog/index OddsPapi API changelog. New features, improvements, bug fixes, and breaking changes. Track WebSocket and REST API updates for sports betting data integration. We continuously improve the Odds API. This page lists **new features**, **changes**, **fixes**, and **breaking changes**. All timestamps are UTC. If a change affects data shape or behavior, it will be marked as **Breaking**. *** ## 2026-05-25 ### ✨ Added * **WebSocket `receiveType: "zstd"` β€” dictless compression.** Opt-in zstd egress with **no dictionary handling**: every data frame is a standalone dictless zstd frame (`dictId 0`), decoded in one line (`zstd.decompress(frame)` β†’ JSON). \~5–6Γ— smaller than JSON on `odds` β€” the simplest way to cut bandwidth. See [Compression](/websocket/compression). * **WebSocket `receiveType: "zstd-dict"` β€” trained dictionaries.** Maximum ratio (\~7–9Γ— on `odds`) using per-channel dictionaries the server pushes as `dict` control frames at connect. ### πŸ” Changed * **`receiveType: "zstd"` now means dictless.** During the zstd beta, `"zstd"` previously delivered trained dictionaries; that behavior moved to the new `"zstd-dict"`. If you were decoding dictionary frames under `"zstd"`, switch to `"zstd-dict"`. * **Dictionary version cache removed.** The `dicts` login field is gone; the server now re-sends the (\~32 KB) dictionaries on every `zstd-dict` connection, so clients no longer persist or version dictionaries across reconnects. A `dicts` field, if still sent, is ignored. *** ## 2026-04-09 ### ✨ Added * **Fixture view: `venue` section** β€” new nested object on all fixture responses with `venueId`, `venueName`, and `venueLocation`. Translated per language. * **Fixture view: `clock` section** β€” new nested object on all fixture responses with `currentPeriod`, `currentTime`, `remainingTime`, `remainingTimeInPeriod`, and `stopped`. All keys present with `null` values until clock data is populated for a fixture. * **Fixture view: `participant1ShortName` / `participant2ShortName`** β€” new fields in the `participants` section of fixture responses, sourced from translated participant data. * **Fixture view: `seasonRound`** β€” new field in the `season` section of fixture responses. * **Futures view: `marketId`** β€” the `market` section in future responses now includes the `marketId` from the futures table. Name fields (`marketName`, `marketType`, `playerMarket`, `participantMarket`) remain `null` for now. * **Participants endpoint: `participantShortName`** β€” the `GET /{lang}/participants` response now includes `participantShortName` for each participant. * New REST endpoint: **`GET /{lang}/venues?venueIds=...`** β€” returns venue data with translated `venueName` and `venueLocation`. * New WebSocket channel: **`clocks`** β€” delivers live clock updates per fixture (same routing as `scores`: filtered by `fixtureId`, sport, tournament). Supports resume/replay. ### ⚠️ Breaking * **Fixture response shape changed** β€” all fixture endpoints (REST and WebSocket) now include three new top-level keys: `venue` (object), `clock` (object), and updated `participants` / `season` sections. Clients parsing fixture responses strictly should update their models. * `season` now includes `seasonRound: integer | null` * `participants` now includes `participant1ShortName: string | null` and `participant2ShortName: string | null` * `venue: { venueId, venueName, venueLocation }` added after `season` * `clock: { currentPeriod, currentTime, remainingTime, remainingTimeInPeriod, stopped }` added after `scores` * **Future response shape changed** β€” `market.marketId` is now populated (integer or null) instead of always `null`. *** ## 2025-12-12 ### ✨ Added * WebSocket **resume & replay** support using `entryId` cursors * New WebSocket channels: * `injuries` * `lineups` * `stats` * AsyncAPI 3.0 reference for WebSocket gateway ### πŸ”§ Improved * Reduced WebSocket latency for `odds` and `scores` * Better filtering for `sportIds`, `tournamentIds`, and `bookmakers` ### πŸ› Fixed * Fixed an issue where some `odds` updates were delivered without bookmaker gating * Fixed incorrect `live` flag on some fixtures during transitions *** ## 2025-11-28 ### ⚠️ Breaking * `odds.payload.odds` keys are now **always bookmaker-scoped** * Old clients expecting a flat structure must update ### ✨ Added * Support for `receiveType: "binary"` (MessagePack) * Added `entryId` to all update messages *** ## 2025-11-10 ### ✨ Added * Initial WebSocket gateway * Channels: * `fixtures` * `scores` * `odds` * `bookmakers` # Sports Coverage - Fixtures & Futures by Sport Source: https://docs.oddspapi.io/coverage Full list of sports covered by the OddsPapi B2B odds API, with fixtures and futures availability per sportId. 60+ sports plus prediction-market topics. OddsPapi covers **60+ sports** plus prediction-market topics, each identified by a numeric `sportId`. Every sport is available as **fixtures** (match-level markets), **futures** (outrights / season-long markets), or both. **Rule of thumb:** `sportId` **10–68** are covered for **both fixtures and futures**. `sportId` **69–78** are prediction-market topics covered for **futures only**. Discover the live list programmatically via `GET /sports`. Identifiers are language-independent; `sportName` follows the request language prefix. ## Coverage by sport | sportId | Sport | Fixtures | Futures | | ------: | ------------------------ | :------: | :-----: | | 10 | Soccer | βœ“ | βœ“ | | 11 | Basketball | βœ“ | βœ“ | | 12 | Tennis | βœ“ | βœ“ | | 13 | Baseball | βœ“ | βœ“ | | 14 | American Football | βœ“ | βœ“ | | 15 | Ice Hockey | βœ“ | βœ“ | | 16 | ESport Dota | βœ“ | βœ“ | | 17 | ESport Counter-Strike | βœ“ | βœ“ | | 18 | ESport League of Legends | βœ“ | βœ“ | | 19 | Darts | βœ“ | βœ“ | | 20 | MMA | βœ“ | βœ“ | | 21 | Boxing | βœ“ | βœ“ | | 22 | Handball | βœ“ | βœ“ | | 23 | Volleyball | βœ“ | βœ“ | | 24 | Snooker | βœ“ | βœ“ | | 25 | Table Tennis | βœ“ | βœ“ | | 26 | Rugby | βœ“ | βœ“ | | 27 | Cricket | βœ“ | βœ“ | | 28 | Waterpolo | βœ“ | βœ“ | | 29 | Futsal | βœ“ | βœ“ | | 30 | Beach Volley | βœ“ | βœ“ | | 31 | Aussie Rules | βœ“ | βœ“ | | 32 | Field hockey | βœ“ | βœ“ | | 33 | Floorball | βœ“ | βœ“ | | 34 | Squash | βœ“ | βœ“ | | 35 | Basketball 3x3 | βœ“ | βœ“ | | 36 | Beach Soccer | βœ“ | βœ“ | | 37 | Pesapallo | βœ“ | βœ“ | | 38 | Lacrosse | βœ“ | βœ“ | | 39 | Curling | βœ“ | βœ“ | | 40 | Padel | βœ“ | βœ“ | | 41 | Bandy | βœ“ | βœ“ | | 42 | Kabaddi | βœ“ | βœ“ | | 43 | Rink Hockey | βœ“ | βœ“ | | 44 | Soccer Specials | βœ“ | βœ“ | | 45 | Gaelic Football | βœ“ | βœ“ | | 46 | Netball | βœ“ | βœ“ | | 47 | Beach Handball | βœ“ | βœ“ | | 48 | Athletics | βœ“ | βœ“ | | 49 | Badminton | βœ“ | βœ“ | | 50 | Bowls | βœ“ | βœ“ | | 51 | Cross-Country | βœ“ | βœ“ | | 52 | Gaelic Hurling | βœ“ | βœ“ | | 53 | Softball | βœ“ | βœ“ | | 54 | eSoccer | βœ“ | βœ“ | | 55 | eBasketball | βœ“ | βœ“ | | 56 | ESport Call of Duty | βœ“ | βœ“ | | 57 | ESport Overwatch | βœ“ | βœ“ | | 58 | ESport Rainbow Six | βœ“ | βœ“ | | 59 | ESport Rocket League | βœ“ | βœ“ | | 60 | ESport StarCraft | βœ“ | βœ“ | | 61 | ESport Valorant | βœ“ | βœ“ | | 62 | ESport Arena of Valor | βœ“ | βœ“ | | 63 | ESport King of Glory | βœ“ | βœ“ | | 64 | Judo | βœ“ | βœ“ | | 65 | ESport Honor of Kings | βœ“ | βœ“ | | 66 | Speedway | βœ“ | βœ“ | | 67 | Golf | βœ“ | βœ“ | | 68 | Cycling | βœ“ | βœ“ | | 69 | Politics | β€” | βœ“ | | 70 | Elections | β€” | βœ“ | | 71 | Economics | β€” | βœ“ | | 72 | Finance | β€” | βœ“ | | 73 | Technology | β€” | βœ“ | | 74 | Health | β€” | βœ“ | | 75 | Science | β€” | βœ“ | | 76 | Cryptocurrency | β€” | βœ“ | | 77 | Weather | β€” | βœ“ | | 78 | Culture | β€” | βœ“ | ## Notes * **Fixtures** are match- or event-level markets (a single game, bout, or match), streamed on the [`fixtures`](/websocket/channels/fixtures) and [`odds`](/websocket/channels/odds) channels. * **Futures** are outright / season-long or topic-level markets (league winner, tournament, election, etc.), streamed on the [`futures`](/websocket/channels/futures) and `oddsFutures` channels. * `sportId` **69–78** are **prediction-market topics** (politics, elections, finance, crypto, weather, …) β€” these exist only as futures/outright markets and feed the prediction-market and exchange use cases. * The `sportId` is the **first two digits** of downstream `marketId`, `outcomeId`, and `fixtureId` values β€” see [Core Concepts](/api-reference/concepts#id-structure). # OddsPapi - B2B Sports Betting Odds API Source: https://docs.oddspapi.io/index B2B low-latency realtime sports odds API. Stream live betting odds, fixtures, scores, lineups, injuries and statistics via WebSocket. REST API for snapshots and historical data. **B2B API** β€” OddsPapi is a business-to-business data service for licensed operators, trading firms, and enterprise platforms. Contact [contact@oddspapi.io](mailto:contact@oddspapi.io) for API access. ## What is OddsPapi? OddsPapi is a **B2B sports data API** providing **low-latency realtime data** including: * Fixtures & schedules * Live scores * Betting odds & markets * Lineups, injuries, stats, and more Built for **trading systems**, **sportsbooks**, **analytics**, and **media platforms**. ### Where OddsPapi fits OddsPapi is, first and foremost, an **aggregated, low-latency realtime trading feed** β€” a WebSocket-first pipe delivering odds from 200+ bookmakers, with snapshots and resume/replay. But it's more than a feed: it covers the wider **betting pipeline**. Alongside realtime prices it provides [historical odds & CLV](/api-reference/concepts#historical-odds-and-clv) and **settlement** endpoints, so clients **build and power full betting products on top of OddsPapi** β€” entire websites and trading systems β€” not just consume a stream. The one thing it deliberately is **not**: an official or licensed league-data provider β€” it aggregates bookmaker prices rather than licensing official stats. Looking for an AI-friendly export? See **AI / Offline Export** or download `/llms-full.txt`. *** ## Get started Connect in minutes and stream realtime data. Subscribe to live odds, scores, and events. REST endpoints for snapshots and metadata. Track updates and breaking changes. *** ## Trusted in production Clients don't just stream the feed β€” they **power entire betting products** on OddsPapi, from realtime pricing through to settlement. Typical clients include: Market makers, hedge funds, and trading syndicates running automated strategies. Betting exchanges and prediction-market platforms sourcing realtime prices. Bookmakers, quant teams, analytics companies, and research institutions. OddsPapi is used in production across prediction-market exchanges, hedge-fund trading desks, and top bookmakers β€” and is proven in live automated betting and market-making projects. *** ## Frequently asked questions Yes. OddsPapi is WebSocket-first and optimized for low-latency realtime trading. It provides per-channel cursors with [resume & replay](/websocket/resume-replay), REST snapshots for recovery, and [historical odds & CLV](/api-reference/concepts#historical-odds-and-clv) for measuring execution. It is used in production for live automated trading. Bookmakers, market makers, hedge funds and trading syndicates, betting exchanges, prediction markets, trading platforms, quant teams, and analytics / research institutions. Clients use it to power entire betting products, not just to stream a feed. Yes. OddsPapi aggregates odds from 200+ bookmakers including prediction markets and exchanges, with orderbook depth (`back` / `lay` ladders and liquidity) exposed in the [odds channel](/websocket/channels/odds) `meta` field. Yes. Settlement endpoints return per-outcome results (won / lost), final scores, and margins for fixtures, so you can grade bets and settle markets β€” closing the pipeline from realtime price through [CLV](/api-reference/concepts#historical-odds-and-clv) to settlement. OddsPapi pairs a fast aggregated realtime bookmaker-odds feed for trading with the pipeline pieces β€” historical odds, CLV, and settlement β€” needed to build full betting products on top. It is not an official or licensed league-data provider; it aggregates bookmaker prices. See [Where OddsPapi fits](#where-oddspapi-fits). Yes. REST endpoints return full price timelines and opening/closing line value (OLV/CLV) for both fixtures and futures β€” built for CLV modelling, fill auditing, and backtesting. See [Historical Odds & CLV](/api-reference/concepts#historical-odds-and-clv). Recovery is bounded and deterministic: short disconnects resume with no data loss, and worst case is a single fast REST snapshot. Bookmaker connectivity is surfaced via the `staleOdds` flag. See [Reliability & Operations](/api-reference/reliability). # Quickstart Guide - Connect to Odds API in Minutes Source: https://docs.oddspapi.io/quickstart Get started with OddsPapi in minutes. Connect to the WebSocket gateway, authenticate with your API key, subscribe to channels, and stream realtime sports odds data. ## 1) Connect Gateway: * **ws**: `wss://v5.oddspapi.io/ws` ```js theme={null} import WebSocket from "ws"; const ws = new WebSocket("wss://v5.oddspapi.io/ws"); ws.on("open", () => { ws.send(JSON.stringify({ type: "login", apiKey: process.env.ODDS_API_KEY })); }); ws.on("message", (data) => { const msg = JSON.parse(data.toString()); console.log(msg.type ?? msg.channel, msg); }); ``` ```py theme={null} import json import os import websocket def on_open(ws): ws.send(json.dumps({ "type": "login", "apiKey": os.environ["ODDS_API_KEY"], })) def on_message(ws, message): msg = json.loads(message) print(msg.get("type") or msg.get("channel"), msg) ws = websocket.WebSocketApp( "wss://v5.oddspapi.io/ws", on_open=on_open, on_message=on_message, ) ws.run_forever() ``` ## 2) Minimal login ```json theme={null} { "type": "login", "apiKey": "YOUR_API_KEY" } ``` # JavaScript SDK - Node.js & Browser Client Source: https://docs.oddspapi.io/sdks/javascript Official OddsPapi JavaScript SDK for Node.js and browser. Connect to WebSocket API, stream realtime odds, and integrate sports betting data into your application. ## Status 🚧 **Coming soon** In the meantime, you can connect using native WebSockets: ```js theme={null} const ws = new WebSocket("wss://v5.oddspapi.io/ws"); ws.onopen = () => { ws.send(JSON.stringify({ type: "login", apiKey: "YOUR_API_KEY" })); }; ``` # Python SDK - Async WebSocket Client Source: https://docs.oddspapi.io/sdks/python Official OddsPapi Python SDK with async WebSocket support. Stream realtime sports betting odds and integrate with your Python trading systems and analytics. ## Status 🚧 **Coming soon** Example using `websockets`: ```python theme={null} import asyncio, json, websockets async def main(): async with websockets.connect("wss://v5.oddspapi.io/ws") as ws: await ws.send(json.dumps({ "type": "login", "apiKey": "YOUR_API_KEY" })) async for msg in ws: print(msg) asyncio.run(main()) ``` # WebSocket Authentication & Channel Filters Source: https://docs.oddspapi.io/websocket/auth Authenticate to OddsPapi WebSocket gateway and configure channel subscriptions. Filter by sports, tournaments, fixtures, and bookmakers for targeted data streaming. ## Login Message Connect and send a `login` message immediately: ```json theme={null} { "type": "login", "apiKey": "YOUR_API_KEY" } ``` **Rules:** * Must be the first message * Send within 10 seconds * Defines all subscriptions and filters *** ## Login with Channels and Filters ```json theme={null} { "type": "login", "apiKey": "YOUR_API_KEY", "receiveType": "binary", "channels": ["fixtures", "scores", "odds"], "sportIds": [10, 11, 12, 13], "tournamentIds": [35430, 39351], "fixtureIds": ["id1103543066138356", "id1103935163991375"], "bookmakers": ["stake"] } ``` *** ## Filter Mode | Field | Type | Description | | --------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `channels` | `string[]` | Streams you want to receive. | | `sportIds` | `number[]` | Restrict to these sports. | | `tournamentIds` | `number[]` | Restrict to these tournaments. | | `fixtureIds` | `string[]` | Exact fixtures (fixture-scoped channels only). | | `futureIds` | `string[]` | Specific futures (future-scoped channels). | | `bookmakers` | `string[]` | Only receive these bookmakers (bookmaker-gated). | | `lang` | `string` | Translations (`en`, `de`, `fr`, etc.). | | `receiveType` | `string` | `"json"` (default) \| `"binary"` (MessagePack) \| `"zstd"` (dictless compressed JSON) \| `"zstd-dict"` (compressed JSON with trained dictionaries). See [Compression](/websocket/compression). | | `clientName` | `string` | Optional debug/metrics tag. | | `serverEpoch` | `string` | For resume. | | `lastSeenId` | `object` | `{ "": "" }` for resume. | > IDs like `fixtureId` and `futureId` are structured but should be treated as opaque in your logic. See [Concepts](/api-reference/concepts). *** ## Access: Live vs Pregame After login, the server tells you what you're allowed to receive: ```json theme={null} { "access": { "live": true, "pregame": false } } ``` These are determined by your `apiKey`, not client filters. *** ## Bookmaker-Gated Channels These channels require explicit bookmaker access: * `odds`, `bookmakers` * `oddsFutures`, `bookmakersFutures` You can restrict which bookmakers you receive: ```json theme={null} { "type": "login", "apiKey": "YOUR_API_KEY", "channels": ["odds", "bookmakers"], "receiveType": "binary", "bookmakers": ["stake", "pinnacle"] } ``` *** ## 🐍 Python Example ```python theme={null} import asyncio, json, websockets, msgpack WS_URL = "wss://v5.oddspapi.io/ws" API_KEY = "your-api-key" LOGIN = { "type": "login", "apiKey": API_KEY, "channels": ["fixtures", "scores", "odds"], "receiveType": "binary", "sportIds": [10, 11], "bookmakers": ["stake"], } async def main(): async with websockets.connect(WS_URL) as ws: await ws.send(json.dumps(LOGIN)) async for raw in ws: if isinstance(raw, str): print("CONTROL:", json.loads(raw)) else: msg = msgpack.unpackb(raw, raw=False) print("DATA:", msg.get("channel"), msg.get("entryId")) asyncio.run(main()) ``` # Bookmakers Channel - Sportsbook Status & Metadata Source: https://docs.oddspapi.io/websocket/channels/bookmakers Stream bookmaker status per fixture via WebSocket. Track which sportsbooks offer odds, detect stale/suspended markets, and monitor participant rotation flags. ## What it streams Metadata about which bookmakers are offering odds for a given fixture, and whether those odds are active, stale, suspended, or rotated. This stream complements the `odds` channel by giving you per-bookmaker status for each `fixtureId`. *** ## Routing * Entity key: `payload.fixtureId` * Filters: `sportIds`, `tournamentIds`, `fixtureIds`, `bookmakers` * Access: determined by your `apiKey` * Bookmaker-gated: βœ… Yes *** ## Payload structure | Field | Type | Description | | --------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | | `fixtureId` | `string` | The fixture this update applies to | | `bookmakers` | `object` | Map of `` β†’ metadata object | | `bookmakers..bookmaker` | `string` | Bookmaker slug (e.g. `"stake"`, `"pinnacle"`) | | `bookmakers..bookmakerFixtureId` | `string \| null` | Optional bookmaker-side ID (may be a slug or compound string) | | `bookmakers..fixturePath` | `string \| null` | Optional bookmaker path or UI route (if supported) | | `bookmakers..hasOdds` | `boolean` | True if the bookmaker currently offers any odds | | `bookmakers..staleOdds` | `boolean` | **Critical for trading.** True if the connection to this bookmaker was lost and odds freshness can no longer be guaranteed | | `bookmakers..staleOddsResponseCode` | `number \| null` | HTTP status code returned during staleness check | | `bookmakers..suspended` | `boolean` | True if this bookmaker's odds are suspended | | `bookmakers..participantsRotated` | `boolean` | True if this bookmaker's home/away assignment differs from the OddsPapi baseline (see below) | | `bookmakers..meta` | `object \| null` | Optional metadata (bookmaker-specific) | | `bookmakers..updatedAt` | `string` | Last update timestamp (ISO 8601) | *** ## Example message ```json theme={null} { "channel": "bookmakers", "type": "UPDATE", "payload": { "fixtureId": "id1000070367118324", "bookmakers": { "draftkings": { "bookmaker": "draftkings", "bookmakerFixtureId": "33999242", "fixturePath": "https://sportsbook.draftkings.com/event/33999242", "hasOdds": true, "staleOdds": false, "staleOddsResponseCode": null, "suspended": false, "participantsRotated": false, "meta": null, "updatedAt": "2026-04-21T00:51:48.283641+00:00" } } }, "ts": 1776729708000, "entryId": "1776729708000-1" } ``` *** ## Critical fields for automated trading ### `staleOdds` β€” odds freshness guarantee If you are running automated trading or arbitrage strategies on top of OddsPapi, `staleOdds` is one of the most important fields you must check. When `staleOdds = true`, the connection between OddsPapi and this bookmaker has been **lost or interrupted**. This means: * Odds you received earlier **may no longer reflect the bookmaker's current prices** * No further updates can be guaranteed until the connection is restored * You should **immediately pause** any automated betting or arbitrage logic for this bookmaker * Treat all existing odds from this bookmaker as **unvalidated** until `staleOdds` returns to `false` `staleOddsResponseCode` provides the HTTP status code (if any) that triggered the staleness detection β€” useful for debugging connectivity issues. ### `participantsRotated` β€” home/away mapping When `participantsRotated = true`, the bookmaker treats a **different team as the home team** than OddsPapi does. In other words, `participant1` (home) and `participant2` (away) are **swapped** at this bookmaker compared to the OddsPapi baseline fixture. This is critical for: * **Moneyline / 1x2 markets** β€” the "home" and "away" outcome meanings are reversed * **Spread / handicap markets** β€” the sign of the spread is flipped * **Any automated strategy** that maps outcomes by participant position rather than participant ID If you build on top of OddsPapi, always check this flag and **swap your participant mapping** for this bookmaker when it is `true`. *** ## Notes * If a bookmaker is not present in `bookmakers`, assume **no current odds available** for that fixture. * Use this channel as a **pre-filter** before processing any odds from the `odds` channel. * Combine `staleOdds`, `suspended`, and `hasOdds` to determine whether a bookmaker's odds should be trusted at any given moment. # Bookmakers Futures Channel - Outright Market Status Source: https://docs.oddspapi.io/websocket/channels/bookmakersFutures Stream bookmaker availability for futures markets via WebSocket. Monitor which sportsbooks offer outright and season-long betting odds. ## What it streams Metadata per bookmaker describing whether a bookmaker offers odds for a given future, and whether those odds are active, stale, or suspended. This is the **future equivalent** of the `bookmakers` channel for fixtures. *** ## Routing * Entity key: `payload.futureId` * Filters: `sportIds`, `tournamentIds`, `futureIds`, `bookmakers` * Access: determined by your `apiKey` * Bookmaker-gated: βœ… Yes *** ## Payload structure | Field | Type | Description | | ----------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | | `futureId` | `string` | The future this update applies to | | `bookmakers` | `object` | Map of `` β†’ metadata object | | `bookmakers..bookmaker` | `string` | Bookmaker slug (e.g. `"stake"`, `"pinnacle"`) | | `bookmakers..bookmakerFutureId` | `string \| null` | Optional bookmaker-side identifier for this future | | `bookmakers..futurePath` | `string \| null` | Optional deeplink or path to the future at the bookmaker | | `bookmakers..hasOdds` | `boolean` | True if the bookmaker currently offers odds | | `bookmakers..staleOdds` | `boolean` | **Critical for trading.** True if the connection to this bookmaker was lost and odds freshness can no longer be guaranteed | | `bookmakers..suspended` | `boolean` | True if the bookmaker has suspended this future | | `bookmakers..meta` | `object \| null` | Optional bookmaker-specific metadata | | `bookmakers..updatedAt` | `string` | Last update timestamp (ISO 8601) | *** ## Example message ```json theme={null} { "channel": "bookmakersFutures", "type": "UPDATE", "payload": { "futureId": "id11028543137888", "bookmakers": { "stake": { "bookmaker": "stake", "bookmakerFutureId": "285431-nbl-new-zealand", "futurePath": null, "hasOdds": true, "staleOdds": false, "suspended": false, "meta": null, "updatedAt": "2025-12-28T18:37:13.719926+00:00" } } }, "ts": 1766947033889, "entryId": "1766947033889-501" } ``` *** ## Critical fields for automated trading ### `staleOdds` β€” odds freshness guarantee If you are running automated trading strategies on top of OddsPapi, `staleOdds` is one of the most important fields you must check. When `staleOdds = true`, the connection between OddsPapi and this bookmaker has been **lost or interrupted**. This means: * Odds you received earlier **may no longer reflect the bookmaker's current prices** * No further updates can be guaranteed until the connection is restored * You should **immediately pause** any automated betting logic for this bookmaker * Treat all existing odds from this bookmaker as **unvalidated** until `staleOdds` returns to `false` *** ## Notes * Odds data itself is streamed via **`oddsFutures`** * If a bookmaker is missing from `bookmakers`, assume **no current odds** * Use this channel as a **pre-filter** before processing any odds from the `oddsFutures` channel * Combine `staleOdds`, `suspended`, and `hasOdds` to determine whether a bookmaker's odds should be trusted * Semantics are intentionally identical to `bookmakers` (fixture-level), just scoped to `futureId` # Clocks Channel - Live Match Clock Updates Source: https://docs.oddspapi.io/websocket/channels/clocks Stream realtime match clock updates via WebSocket. Period, remaining time, and stopped state for live fixtures. ## What it streams Realtime clock updates scoped to a specific `fixtureId`. Each message contains the current clock state for a live match β€” period, elapsed/remaining time, and whether the clock is stopped. *** ## Routing * Entity key: `payload.fixtureId` * Filters: `sportIds`, `tournamentIds`, `fixtureIds` * Access: live/pregame determined by your `apiKey` *** ## Payload fields | Field | Type | Description | | ----------------------------- | ----------------- | -------------------------------------- | | `fixtureId` | `string` | The fixture this clock applies to | | `clock` | `object` | Clock state | | `clock.currentPeriod` | `string \| null` | Current period (e.g. `"p1"`, `"p2"`) | | `clock.currentTime` | `string \| null` | Current match time | | `clock.remainingTime` | `string \| null` | Remaining time in the match | | `clock.remainingTimeInPeriod` | `string \| null` | Remaining time in the current period | | `clock.stopped` | `boolean \| null` | Whether the clock is currently stopped | *** ## Example message (JSON) ```json theme={null} { "channel": "clocks", "type": "UPDATE", "payload": { "fixtureId": "id1500023462078980", "clock": { "currentPeriod": "p2", "currentTime": null, "remainingTime": "26:42", "remainingTimeInPeriod": "2:42", "stopped": false } }, "ts": 1775170900000, "entryId": "1775170900000-42" } ``` *** ## Notes * Clock updates are delivered independently from `scores` and `fixtures` * The `clock` object is also included in the `fixtures` channel payload (as part of the fixture snapshot) * When no clock data is available, all fields will be `null` * Use this channel for high-frequency clock state without receiving the full fixture payload # Currencies Channel - Exchange Rates & Crypto Prices Source: https://docs.oddspapi.io/websocket/channels/currencies Stream realtime currency exchange rates via WebSocket. Fiat and cryptocurrency pricing data including BTC, ETH, and major tokens for odds conversion. ## What it streams Currency/ticker exchange values (vs a reference currency) for fiat and crypto. This is a **global channel** β€” it is not scoped to any fixture or future. *** ## Routing * Channel: `currencies` (global) * Filters: ❌ ignored * Access: allowed for all keys (unless specifically disabled) *** ## Payload structure Each message contains a flat list of currency values: | Field | Type | Description | | --------------- | -------------- | ------------------------------------------------------- | | `currency` | `string` | Symbol of the currency/token (e.g. `BTC`, `USD`, `ADA`) | | `currencyValue` | `number` | Current exchange rate (float) | | `updatedAt` | `string (ISO)` | Time this value was last refreshed | *** ## Example message ```json theme={null} { "channel": "currencies", "type": "UPDATE", "payload": { "BTC": { "currency": "BTC", "currencyValue": 0.00001141, "updatedAt": "2025-12-28T18:38:02+00:00" } }, "ts": 1766939809876, "entryId": "1766939809876-700" } ``` *** ## Notes * All values are relative to the system's reference currency: USD # Fixtures Channel - Match Metadata & Schedules Source: https://docs.oddspapi.io/websocket/channels/fixtures Stream realtime fixture updates via WebSocket. Match status, start times, participants, tournaments, scores, and provider ID mappings for Betradar, Pinnacle, Flashscore. ## What it streams Metadata for a given `fixtureId` β€” including match status, start times, participants, and tournament/sport info. Also includes optional `scores` and mapped provider IDs. This is the **anchor stream** used to map odds/scores to a real-world fixture. *** ## Routing * Entity key: `payload.fixtureId` * Filters: `sportIds`, `tournamentIds`, `fixtureIds` * Access: live/pregame determined by your `apiKey` *** ## Payload fields | Field | Type | Description | | ------------------------------------ | ----------------- | ------------------------------------------------------- | | `fixtureId` | `string` | Unique ID of the fixture | | `status` | `object` | Match status info | | `status.live` | `boolean` | True if currently live | | `status.statusId` | `number \| null` | Optional status code | | `status.statusName` | `string \| null` | Status name (e.g. "Live", "Postponed") | | `sport` | `object` | Sport metadata | | `sport.sportId` | `number` | Unique sport ID | | `sport.sportName` | `string` | Sport name | | `tournament` | `object` | Tournament metadata | | `tournament.tournamentId` | `number` | Unique tournament ID | | `tournament.tournamentName` | `string` | Tournament name | | `tournament.categoryName` | `string` | Geographic/organizational category | | `season` | `object` | Season metadata | | `season.seasonId` | `number \| null` | Season ID (nullable) | | `season.seasonName` | `string \| null` | Season name | | `season.seasonRound` | `number \| null` | Round/matchday number within the season | | `venue` | `object` | Venue metadata | | `venue.venueId` | `number \| null` | Venue ID | | `venue.venueName` | `string \| null` | Venue name (translated) | | `venue.venueLocation` | `string \| null` | Venue location (translated) | | `startTime` | `number` | Scheduled start (epoch seconds UTC) | | `trueStartTime` | `string \| null` | Actual start time (ISO 8601) | | `trueEndTime` | `string \| null` | Actual end time (ISO 8601) | | `participants` | `object` | Competitor metadata | | `participants.participant1Id` | `number` | Team/player 1 ID | | `participants.participant1Name` | `string` | Team/player 1 name | | `participants.participant1ShortName` | `string \| null` | Short name (e.g. "Hawks") | | `participants.participant1Abbr` | `string \| null` | Optional abbreviation | | `participants.participant1RotNr` | `number \| null` | Optional rotation number | | `participants.participant2Id` | `number` | Team/player 2 ID | | `participants.participant2Name` | `string` | Team/player 2 name | | `participants.participant2ShortName` | `string \| null` | Short name (e.g. "Heat") | | `participants.participant2Abbr` | `string \| null` | Optional abbreviation | | `participants.participant2RotNr` | `number \| null` | Optional rotation number | | `scores` | `object` | Optional score object (same format as `scores` stream) | | `clock` | `object \| null` | Live match clock data | | `clock.currentPeriod` | `string \| null` | Current period (e.g. "p2") | | `clock.currentTime` | `string \| null` | Current match time | | `clock.remainingTime` | `string \| null` | Remaining time in match | | `clock.remainingTimeInPeriod` | `string \| null` | Remaining time in current period | | `clock.stopped` | `boolean \| null` | Whether the clock is stopped | | `expectedPeriods` | `number \| null` | Number of scheduled periods (e.g. halves/sets/quarters) | | `periodLength` | `number \| null` | Period duration in minutes | | `externalProviders` | `object` | Mapped provider IDs | | `externalProviders.betradarId` | `number \| null` | Betradar fixture ID | | `externalProviders.flashscoreId` | `string \| null` | Flashscore ID | | `externalProviders.pinnacleId` | `number \| null` | Pinnacle fixture ID | | `externalProviders.sofascoreId` | `number \| null` | Sofascore ID | | `externalProviders.oddinId` | `number \| null` | Oddin ID | | `externalProviders.mollybetId` | `string \| null` | Mollybet ID | | `externalProviders.opticoddsId` | `string \| null` | Opticodds ID | | `externalProviders.lsportsId` | `number \| null` | LSports ID | | `externalProviders.txoddsId` | `number \| null` | TXOdds ID | *** ## Example message (JSON) ```json theme={null} { "channel": "fixtures", "type": "UPDATE", "payload": { "fixtureId": "id1100013270505056", "status": { "live": true, "statusId": 1, "statusName": "Live" }, "sport": { "sportId": 11, "sportName": "Basketball" }, "tournament": { "tournamentId": 132, "tournamentName": "NBA", "categoryName": "USA" }, "season": { "seasonId": 131631, "seasonName": "NBA 25/26", "seasonRound": null }, "venue": { "venueId": 6054, "venueName": "Madison Square Garden", "venueLocation": "New York, NY, USA" }, "startTime": 1776729600, "trueStartTime": "2026-04-21T00:09:08+00:00", "trueEndTime": null, "participants": { "participant1Id": 3421, "participant1RotNr": 303, "participant1Name": "New York Knicks", "participant1ShortName": "New York", "participant1Abbr": "NYK", "participant2Id": 3423, "participant2RotNr": 302, "participant2Name": "Atlanta Hawks", "participant2ShortName": "Atlanta", "participant2Abbr": "ATL" }, "scores": { "p1": { "period": "p1", "participant1Score": 32, "participant2Score": 23, "updatedAt": "2026-04-21T00:59:42.287458+00:00" }, "p2": { "period": "p2", "participant1Score": 29, "participant2Score": 31, "updatedAt": "2026-04-21T01:25:05.532295+00:00" }, "result": { "period": "result", "participant1Score": 61, "participant2Score": 54, "updatedAt": "2026-04-21T01:24:37.154297+00:00" } }, "clock": { "currentPeriod": null, "currentTime": null, "remainingTime": null, "remainingTimeInPeriod": null, "stopped": null }, "expectedPeriods": 4, "periodLength": 12, "externalProviders": { "betgeniusId": 13808265, "betradarId": 70505056, "flashscoreId": "nTPkYS3K", "mollybetId": "2026-04-21,29093,29096", "oddinId": null, "opticoddsId": "20260421A1AB4678", "pinnacleId": 1628730839, "sofascoreId": 15935017, "lsportsId": null, "txoddsId": null }, "bookmakers": {} }, "ts": 1776729608000, "entryId": "1776729608000-4273" } ``` ## Example: fixture update that includes a `scores.result` Some fixture updates include a `scores` object (for example, a pre-game or result snapshot). ```json theme={null} { "channel": "fixtures", "type": "UPDATE", "payload": { "fixtureId": "id1000070367118324", "status": { "live": true, "statusId": 1, "statusName": "Live" }, "sport": { "sportId": 10, "sportName": "Soccer" }, "tournament": { "tournamentId": 703, "tournamentName": "Primera Nacional", "categoryName": "Argentina" }, "season": { "seasonId": 138218, "seasonName": "Primera Nacional 2026", "seasonRound": 10 }, "venue": { "venueId": 12720, "venueName": "Estadio Don Leon Kolbovsky", "venueLocation": "Buenos Aires, Argentina" }, "startTime": 1776729600, "trueStartTime": "2026-04-21T00:06:06.546877+00:00", "trueEndTime": null, "participants": { "participant1Id": 53799, "participant1RotNr": 210458, "participant1Name": "CA Atlanta", "participant1ShortName": "Atlanta", "participant1Abbr": "ATL", "participant2Id": 3214, "participant2RotNr": 210457, "participant2Name": "Chacarita Juniors", "participant2ShortName": "Chacarita Juniors", "participant2Abbr": "CAC" }, "scores": { "p1": { "period": "p1", "participant1Score": 0, "participant2Score": 0, "updatedAt": "2026-04-21T00:55:10.401457+00:00" }, "result": { "period": "result", "participant1Score": 1, "participant2Score": 0, "updatedAt": "2026-04-21T01:13:15.757548+00:00" } }, "clock": { "currentPeriod": null, "currentTime": null, "remainingTime": null, "remainingTimeInPeriod": null, "stopped": null }, "expectedPeriods": 2, "periodLength": 45, "externalProviders": { "betgeniusId": 13767372, "betradarId": 67118324, "flashscoreId": null, "mollybetId": "2026-04-21,10097858,10097878", "oddinId": null, "opticoddsId": "202604183517C8A7", "pinnacleId": 1628328116, "sofascoreId": 15275552, "lsportsId": null, "txoddsId": null }, "bookmakers": {} }, "ts": 1776729666000, "entryId": "1776729666000-4278" } ``` # Futures Channel - Season & Outright Markets Source: https://docs.oddspapi.io/websocket/channels/futures Stream futures and outright betting markets via WebSocket. Season-long markets, tournament winners, championship odds with timing windows and provider mappings. ## What it streams Metadata about long-term or season-based betting markets (called β€œfutures”), scoped to a `futureId`. Includes tournament/season mapping, market metadata, timing window, and external provider IDs. *** ## Routing * Entity key: `payload.futureId` * Filters: `sportIds`, `tournamentIds`, `futureIds` * Access: live vs pregame determined by your `apiKey` *** ## Payload fields | Field | Type | Description | | -------------------------------- | ---------------- | --------------------------------------------------- | | `futureId` | `string` | Unique identifier for the future | | `status` | `object` | Live/pregame status | | `status.live` | `boolean` | Whether the future is currently live | | `status.statusId` | `number \| null` | Optional status code | | `status.statusName` | `string \| null` | Optional status label (e.g. `"Pre-Game"`, `"Live"`) | | `sport` | `object` | Sport metadata | | `sport.sportId` | `number` | Unique sport ID | | `sport.sportName` | `string` | Human-readable sport name | | `tournament` | `object` | Tournament metadata | | `tournament.tournamentId` | `number` | Unique tournament ID | | `tournament.tournamentName` | `string` | Tournament name | | `tournament.categoryName` | `string` | Geographic/organizational grouping | | `season` | `object` | Season metadata | | `season.seasonId` | `number \| null` | Season ID (nullable) | | `season.seasonName` | `string \| null` | Season name | | `startTime` | `number` | Start of the betting window (epoch seconds UTC) | | `endTime` | `number` | End of the betting window (epoch seconds UTC) | | `market` | `object \| null` | Market metadata (nullable) | | `market.marketId` | `number \| null` | Optional market ID | | `market.marketName` | `string \| null` | Optional market name (e.g. `"Outright Winner"`) | | `market.marketType` | `string \| null` | Optional market type code | | `participants` | `array` | List of participants for this future (may be empty) | | `externalProviders` | `object` | Mapped external provider IDs | | `externalProviders.betradarId` | `number \| null` | Betradar ID | | `externalProviders.flashscoreId` | `string \| null` | Flashscore ID | | `externalProviders.opticoddsId` | `string \| null` | OpticOdds ID | | `externalProviders.polymarketId` | `string \| null` | Polymarket ID | | `externalProviders.kalshiId` | `string \| null` | Kalshi ID | | `externalProviders.sofascoreId` | `number \| null` | Sofascore ID | | `bookmakers` | `object` | Odds availability status (see `bookmakersFutures`) | *** ## Example: future metadata ```json theme={null} { "channel": "futures", "type": "UPDATE", "payload": { "futureId": "pm6980037088158379224", "status": { "live": true, "statusId": 1, "statusName": "Live" }, "sport": { "sportId": 69, "sportName": "Politics" }, "tournament": { "tournamentId": 800370, "tournamentName": "2025 Predictions", "categoryName": "Politics" }, "season": { "seasonId": 8815837922, "seasonName": "Macron out by...?" }, "startTime": 1735932904, "endTime": 1782820800, "market": { "marketId": 4, "marketName": null, "marketType": null }, "participants": [], "externalProviders": { "betradarId": null, "flashscoreId": null, "opticoddsId": null, "polymarketId": "16263", "kalshiId": null, "sofascoreId": null }, "bookmakers": {} }, "ts": 1776939800000, "entryId": "1776939800000-999" } ``` ## Example: sports future ```json theme={null} { "channel": "futures", "type": "UPDATE", "payload": { "futureId": "id100000281190131", "status": { "live": true, "statusId": 1, "statusName": "Live" }, "sport": { "sportId": 10, "sportName": "Soccer" }, "tournament": { "tournamentId": 28, "tournamentName": "AFC Asian Cup QF", "categoryName": "International" }, "season": { "seasonId": 119013, "seasonName": "AFC Asian Cup QF 2026" }, "startTime": 1725494400, "endTime": 1780617599, "market": { "marketId": 1, "marketName": null, "marketType": null }, "participants": [], "externalProviders": { "betradarId": 119013, "flashscoreId": null, "opticoddsId": null, "polymarketId": null, "kalshiId": null, "sofascoreId": null }, "bookmakers": {} }, "ts": 1776939800100, "entryId": "1776939800100-1000" } ``` *** ## Notes * Odds are streamed separately via the [`oddsFutures`](/websocket/channels/oddsFutures) channel * Use `futureId` to join with: * `oddsFutures` (for prices) * `bookmakersFutures` (for status per bookmaker) * Most `market` fields may be `null` for legacy or non-structured markets # Odds Channel - Realtime Betting Odds Stream Source: https://docs.oddspapi.io/websocket/channels/odds High-throughput realtime betting odds via WebSocket. Stream live odds aggregated from 200+ bookmakers. Decimal, fractional, American formats with orderbook depth. ## What it streams Realtime odds updates for outcomes within markets, scoped to a specific `fixtureId`. Odds are grouped by bookmaker and keyed by a unique **oddsId** per outcome. Each entry represents a single **oddsId** (one price for one outcome). *** ## Routing * Entity key: `payload.fixtureId` * Filters: `sportIds`, `tournamentIds`, `fixtureIds`, `bookmakers` * Access: determined by your `apiKey` * Bookmaker-gated: βœ… Yes *** ## Delivery semantics * This channel is **high throughput** * Updates are **latest-state only** * The gateway may coalesce or drop intermediate updates under load * Do **not** assume tick-by-tick completeness Treat every message as a **state update**, not a ledger. > Need every price move or the closing line? The stream is for trading on latest state; for full movement use [Historical Odds](/api-reference/concepts#historical-odds-and-clv) and for opening/closing prices use [CLV](/api-reference/concepts#historical-odds-and-clv). *** ## Payload structure oddsId: ``` ::: ``` *** ## outcome object (full schema) Each odds entry is an **outcome**, with the following fields: | Field | Type | Description | | | -------------------- | ----------------- | ------------------------------------------------------------- | ----------------------------------- | | `bookmaker` | `string` | Bookmaker slug (e.g. `"stake"`, `"pinnacle"`, `"polymarket"`) | | | `outcomeId` | `integer` | Outcome identifier | | | `playerId` | `integer` | Player ID (`0` for non-player markets) | | | `price` | `number` | Decimal odds | | | `active` | `boolean` | Whether this outcome is currently available | | | `marketActive` | `boolean \| null` | Whether the entire market is active | | | `mainLine` | `boolean \| null` | Whether this is the bookmaker’s main line | | | `marketId` | `integer` | Market identifier | | | `bookmakerMarketId` | `string \| null` | Native bookmaker market ID | | | `bookmakerOutcomeId` | `string \| null` | Native bookmaker outcome ID | | | `bookmakerChangedAt` | `number \| null` | Bookmaker-provided change timestamp (epoch ms) | | | `priceFractional` | \`string | \` | Fractional odds (e.g. `"5/2"`) | | `priceAmerican` | \`integer | \` | American odds (e.g. `-110`, `+250`) | | `limit` | `number \| null` | Maximum accepted stake (if provided) | | | `betslip` | `string \| null` | Optional bookmaker betslip or deeplink token | | | `meta` | `object \| null` | Bookmaker-specific metadata (orderbooks, ladders, etc.) | | | `changedAt` | `number` | Gateway change timestamp (epoch ms, UTC) | | *** ## Notes on timestamps * `changedAt` is **always present** and represents when the gateway accepted the update * `bookmakerChangedAt` (when present) reflects the bookmaker’s own timestamp * These values may differ β€” do not assume equality *** ## Advanced metadata (`meta`) Some bookmakers (e.g. prediction markets / exchanges) provide rich metadata. Example: ```json theme={null} { "meta": { "lay": [ { "price": 2.00, "size": 100.0 }, { "price": 2.10, "size": 80.0 } ], "back": [ { "price": 1.95, "size": 50.0 } ] } } ``` Typical `meta` contents may include: * Orderbook ladders (`back` / `lay`) * Liquidity hints * Internal sizing or tick metadata The schema of `meta` is **bookmaker-specific** and may evolve. *** ## Example: traditional bookmaker odds ```json theme={null} { "channel": "odds", "type": "UPDATE", "payload": { "fixtureId": "id1100013270505136", "odds": { "pinnacle": { "id1100013270505136:pinnacle:111:0": { "bookmaker": "pinnacle", "outcomeId": 111, "playerId": 0, "active": true, "price": 1.155, "marketActive": true, "mainLine": true, "bookmakerMarketId": "line/4/487/1628488896/3565645414/0/moneyline", "bookmakerOutcomeId": "home", "bookmakerChangedAt": 1776717657043, "limit": 19354, "priceAmerican": -645, "priceFractional": "11/71", "marketId": 111, "changedAt": 1776717657402 }, "id1100013270505136:pinnacle:112:0": { "bookmaker": "pinnacle", "outcomeId": 112, "playerId": 0, "active": true, "price": 5.77, "marketActive": true, "mainLine": true, "bookmakerMarketId": "line/4/487/1628488896/3565645414/0/moneyline", "bookmakerOutcomeId": "away", "bookmakerChangedAt": 1776717657043, "limit": 3000, "priceAmerican": 477, "priceFractional": "477/100", "marketId": 111, "changedAt": 1776717657402 } } } }, "ts": 1776717657500, "entryId": "1776717657500-84805" } ``` *** ## Example: prediction market odds (extended fields) ```json theme={null} { "channel": "odds", "type": "UPDATE", "payload": { "fixtureId": "id1100064864029581", "odds": { "polymarket": { "id1100064864029581:polymarket:112:0": { "bookmaker": "polymarket", "outcomeId": 112, "playerId": 0, "active": true, "price": 6.369, "priceAmerican": 537, "limit": 4.71, "bookmakerMarketId": "1011497", "bookmakerOutcomeId": "4937...", "meta": { "back": [{ "price": 6.369, "size": 30.0 }], "lay": [{ "price": 100.0, "size": 15.0 }] }, "marketActive": true, "changedAt": 1766939876376 } } } }, "ts": 1766939876700, "entryId": "1766939876700-653" } ``` *** ## Implementation guidance * Always key storage by ``` {fixtureId}:{bookmaker}:{outcomeId}:{playerId} ``` * Group outcomes by `marketId` (see **Concepts**) for: * arbitrage detection * overround calculations * probability normalization * Treat `active=false` or `marketActive=false` as **hard stops** * Preserve unknown fields in `meta` to remain forward-compatible *** # Scores Channel - Live Score Updates Source: https://docs.oddspapi.io/websocket/channels/scores Stream realtime live scores via WebSocket. Period-by-period score updates for all sports including result, halftime, quarters, sets, and periods. ## What it streams Realtime score updates scoped to a specific `fixtureId`. Each `payload.scores` object is keyed by the period (`result`, `p1`, etc.). *** ## Routing * Entity key: `payload.fixtureId` * Filters: `sportIds`, `tournamentIds`, `fixtureIds` * Access: live/pregame access is determined by your `apiKey` *** ## Payload structure | Field | Type | Description | | ---------------- | -------------- | -------------------------------------- | | `fixtureId` | `string` | The fixture this score applies to | | `scores` | `object` | Scores per period | | `scores[period]` | `object` | Each period's score and metadata | | `updatedAt` | `string (ISO)` | Last known update time for this period | *** ## Example: result update ```json theme={null} { "channel": "scores", "type": "UPDATE", "payload": { "fixtureId": "id1500025662664057", "scores": { "result": { "period": "result", "participant1Score": 2, "participant2Score": 1, "updatedAt": "2025-12-28T16:24:11.426852+00:00" } } }, "ts": 1766939805321, "entryId": "1766939805321-3077" } ``` ## Example: multiple period updates ```json theme={null} { "channel": "scores", "type": "UPDATE", "payload": { "fixtureId": "id2503637767171366", "scores": { "p1": { "period": "p1", "participant1Score": 7, "participant2Score": 6, "updatedAt": "2025-12-28T16:34:02.865934+00:00" }, "result": { "period": "result", "participant1Score": 0, "participant2Score": 0, "updatedAt": "2025-12-28T16:32:18.616082+00:00" } } }, "ts": 1766939642953, "entryId": "1766939642953-1905" } ``` *** ## Notes * Periods may include `result`, `p1`, `1stHalf`, etc. * Use `updatedAt` to detect stale scores * This stream **does not** include match status (see `fixtures`) # WebSocket Compression (zstd) Source: https://docs.oddspapi.io/websocket/compression Cut WebSocket bandwidth with zstd. Two opt-in modes: receiveType: zstd (dictless, decode in one line) or receiveType: zstd-dict (trained per-channel dictionaries, best ratio). **Beta β€” rolling out per region.** zstd is being enabled gradually. If a gateway has it disabled, a `receiveType: "zstd"` or `"zstd-dict"` connection is **gracefully downgraded to JSON** β€” the server tells you the negotiated mode in `login_ok.receiveType`. **Always trust that echo** to choose your decoder; never assume the mode you requested. ## Two modes Both compress every data frame with zstd; control frames stay JSON text. Pick based on how much client work you want to do: | `receiveType` | Ratio on `odds` | Client work | `dict` frames | | ------------- | --------------- | ------------------------------------------------------------------------------------ | --------------- | | `"zstd"` | \~5–6Γ— | **One line:** `zstd.decompress(frame)` β†’ JSON | none | | `"zstd-dict"` | \~7–9Γ— | Cache the dictionaries the server pushes, decode each frame by its embedded `dictId` | sent at connect | **Start with `"zstd"`.** It needs no dictionary handling at all β€” decompress and parse, done. Move to `"zstd-dict"` when you want the extra \~30–40% and can keep a small in-memory dictionary store. > There is intentionally **no** compressed-MessagePack mode β€” compressing JSON beats compressing > MessagePack at every level. *** ## Mode 1 β€” `zstd` (dictless) The soft-adoption path. Every data frame is a standalone, dictless zstd frame (`dictId = 0`); the server sends **no** `dict` control frames. Your client only decompresses and parses. ### Login ```json theme={null} { "type": "login", "apiKey": "YOUR_API_KEY", "channels": ["odds", "fixtures"], "receiveType": "zstd" } ``` ### Decode ``` on text frame β†’ JSON.parse β†’ control (login_ok, error, …) on binary frame β†’ JSON.parse(zstd.decompress(frame)) // no dictionary needed route by msg.channel ``` That's the whole protocol for this mode. *** ## Mode 2 β€” `zstd-dict` (trained dictionaries) The maximum-ratio path. `odds`, `fixtures`, and `bookmakers` have trained \~32 KB dictionaries that push the ratio to \~7–9Γ—. The server delivers them as control frames at connect; each data frame embeds the `dictId` it was compressed with, so decoding is **self-describing and never branches on channel**. ### Login ```json theme={null} { "type": "login", "apiKey": "YOUR_API_KEY", "channels": ["odds", "fixtures"], "receiveType": "zstd-dict" } ``` ### Dictionary delivery (server β†’ client) Right after `login_ok` (and before any data frame), for each subscribed channel that has a trained dictionary the server pushes one control frame: ```json theme={null} { "type": "dict", "channel": "odds", "dictVersion": "odds-v1", "dictId": 740826216, "encoding": "base64", "data": "" } ``` 1. Base64-decode `data` to the raw dictionary bytes. 2. Build a reusable zstd decoder from it, stored **keyed by `dictId`**. Decoding is driven by the **`dictId` embedded in each data frame**, not by channel. The `channel`/`dictVersion` fields on the `dict` frame are informational. The dictionaries are small (\~32 KB) and re-sent on **every** connection β€” there is no client-side version cache to maintain and no `dicts` field to send at login. Channels without a trained dictionary (e.g. `scores`, `clocks`) send no `dict` frame; their data frames carry `dictId = 0` and decode dictless. ### Decode ``` on text frame β†’ JSON.parse β†’ control (login_ok, dict, error, …) on binary frame β†’ id = zstd.getDictID(frame) // 0 β‡’ no dict dict = store[id] // undefined β‡’ dictless msg = JSON.parse(zstd.decompress(frame, dict)) route by msg.channel ``` *** ## Frame rules (both modes) * Every data frame is a WebSocket **Binary** frame containing one standalone zstd frame (magic `28 B5 2F FD`, with an embedded `dictId`). * Control frames (`login_ok`, `dict`, `error`, `snapshot_required`, `resume_complete`) stay **JSON text frames**, even on a zstd connection. The rule is fixed: > **Text frame β†’ control (JSON). Binary frame β†’ data (zstd).** `login_ok.receiveType` is the **negotiated** mode (`"zstd"`, `"zstd-dict"`, or β€” if the gateway has zstd disabled β€” `"json"`). If it comes back `"json"`, decode plain JSON text frames; do **not** try to decompress. *** ## 🐍 Python examples Requires `pip install zstandard`. ```python theme={null} import asyncio, json, websockets import zstandard as zstd WS_URL, API_KEY = "wss://v5.oddspapi.io/ws", "YOUR_API_KEY" dctx = zstd.ZstdDecompressor() MAX_OUT = 64 * 1024 * 1024 # frames carry no content size β€” pass an upper bound async def main(): async with websockets.connect(WS_URL, max_size=4194304) as ws: await ws.send(json.dumps({ "type": "login", "apiKey": API_KEY, "channels": ["odds", "fixtures"], "receiveType": "zstd", })) async for raw in ws: if isinstance(raw, str): # control (JSON) msg = json.loads(raw) if msg.get("type") == "login_ok" and msg.get("receiveType") != "zstd": print("zstd disabled here, negotiated:", msg.get("receiveType")) continue data = json.loads(dctx.decompress(raw, max_output_size=MAX_OUT)) print("DATA:", data.get("channel"), data.get("entryId")) asyncio.run(main()) ``` ```python theme={null} import asyncio, json, base64, websockets import zstandard as zstd WS_URL, API_KEY = "wss://v5.oddspapi.io/ws", "YOUR_API_KEY" decoders: dict[int, zstd.ZstdDecompressor] = {} # dictId -> decoder dictless = zstd.ZstdDecompressor() MAX_OUT = 64 * 1024 * 1024 def decode_frame(frame: bytes) -> dict: dict_id = zstd.get_frame_parameters(frame).dict_id # 0 if no dictionary dctx = decoders.get(dict_id, dictless) return json.loads(dctx.decompress(frame, max_output_size=MAX_OUT)) async def main(): async with websockets.connect(WS_URL, max_size=4194304) as ws: await ws.send(json.dumps({ "type": "login", "apiKey": API_KEY, "channels": ["odds", "fixtures"], "receiveType": "zstd-dict", })) async for raw in ws: if isinstance(raw, str): # control (JSON) msg = json.loads(raw) t = msg.get("type") if t == "login_ok" and msg.get("receiveType") != "zstd-dict": print("downgraded, negotiated:", msg.get("receiveType")) elif t == "dict": d = zstd.ZstdCompressionDict(base64.b64decode(msg["data"])) decoders[msg["dictId"]] = zstd.ZstdDecompressor(dict_data=d) continue data = decode_frame(raw) # data (zstd) print("DATA:", data.get("channel"), data.get("entryId")) asyncio.run(main()) ``` *** ## See also * [Auth & Filters](/websocket/auth) β€” full `login` field reference * [Resume & Replay](/websocket/resume-replay) β€” reconnecting and recovering missed data * [Troubleshooting](/websocket/troubleshooting) β€” zstd decoding issues # Interactive WebSocket Console - Test Live Data Source: https://docs.oddspapi.io/websocket/console Test OddsPapi WebSocket API in your browser. Interactive console for connecting to the gateway, subscribing to channels, and viewing realtime sports data.