openapi: 3.2.0 info: title: Odds Catalog 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: Catalog description: Supported sports, leagues, bookmakers, and approximate market coverage. paths: /sports: get: tags: - Catalog summary: 'Catalog: Sports' operationId: sports_sports_get security: - ApiKeyAuth: [] parameters: [] responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/StringListResponse' '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: Lists sports with event and odds coverage. x-rate-limit-note: Handle HTTP 429 with backoff and avoid tight polling loops. /leagues: get: tags: - Catalog summary: 'Catalog: Leagues' operationId: leagues_leagues_get security: - ApiKeyAuth: [] parameters: - name: sport in: query required: false schema: anyOf: - type: string - type: 'null' title: Sport description: Sport filter. Use `/sports` to discover supported values. responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/StringListResponse' '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: Lists available leagues. Pass `sport` to narrow the response. x-rate-limit-note: Handle HTTP 429 with backoff and avoid tight polling loops. /bookmakers: get: tags: - Catalog summary: 'Catalog: Bookmakers' operationId: bookmakers_bookmakers_get security: - ApiKeyAuth: [] parameters: - name: country_code in: query required: false schema: anyOf: - type: string - type: 'null' description: Optional comma-separated country code filter, for example `AU` or `AU,UK`. title: Country Code description: Comma-separated country code filter, for example `AU` or `AU,UK`. responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/BookmakerCatalogResponse' examples: default: summary: 'Catalog: Bookmakers' value: items: - bookmaker: bet365 country_codes: - AU - UK - bookmaker: pinnacle country_codes: - US '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: Lists active bookmakers accepted by bookmaker filters across odds and betting endpoints. Each item includes the country codes where that bookmaker is available. Pass `country_code=AU` or `country_code=AU,UK` to filter the catalog. x-rate-limit-note: Handle HTTP 429 with backoff and avoid tight polling loops. /bookmakers/countries: get: tags: - Catalog summary: 'Catalog: Bookmaker countries' operationId: bookmaker_countries_bookmakers_countries_get security: - ApiKeyAuth: [] parameters: [] responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/BookmakerCountriesCatalogResponse' examples: default: summary: 'Catalog: Bookmaker countries' value: items: - country_code: AU country: Australia bookmakers: - bet365 - sportsbet - country_code: UK country: United Kingdom bookmakers: - bet365 '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: Lists country codes represented in the active bookmaker catalog and the bookmakers available in each country. x-rate-limit-note: Handle HTTP 429 with backoff and avoid tight polling loops. /coverage: get: tags: - Catalog summary: 'Catalog: Coverage' operationId: coverage_coverage_get security: [] parameters: - name: bookmaker in: query required: false schema: anyOf: - type: string - type: 'null' description: Canonical bookmaker filter, for example `bet365`. title: Bookmaker description: Canonical bookmaker filter. Use `/bookmakers` or `/coverage` to discover supported keys. - name: sport in: query required: false schema: anyOf: - type: string - type: 'null' description: Sport filter, for example `basketball`. title: Sport description: Sport filter. Use `/sports` to discover supported values. - name: league in: query required: false schema: anyOf: - type: string - type: 'null' description: League filter, for example `NBA`. title: League description: League filter. Use `/leagues?sport=...` to discover supported values. - name: country_code in: query required: false schema: anyOf: - type: string - type: 'null' description: Optional comma-separated country code filter. title: Country Code description: Comma-separated country code filter, for example `AU` or `AU,UK`. - name: lookback_days in: query required: false schema: type: integer maximum: 90 minimum: 1 default: 30 title: Lookback Days description: Number of days of recently observed approximate market coverage to include. Maximum is 90. responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/CoverageResponse' examples: default: summary: 'Catalog: Coverage' value: as_of: '2026-04-29T10:25:00Z' bookmakers: - bookmaker: bet365 country_codes: - AU - UK - bookmaker: sportsbet country_codes: - AU sports: - basketball - rugby league leagues: - sport: basketball league: NBA - sport: rugby league league: NRL markets: - bookmaker: bet365 sport: basketball league: NBA bet_type: moneyline last_seen_at: '2026-04-29T10:20:00Z' sample_event_id: '3704597661' - bookmaker: sportsbet sport: rugby league league: NRL bet_type: total metric: tries last_seen_at: '2026-04-29T10:18:00Z' sample_event_id: '3704597662' source: markets_are_approximate: true lookback_days: 30 '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 public bookmaker, sport, league, and recently observed market coverage. Market records are approximate and based on normalized odds lines seen in the configured lookback window, not a guarantee that every market is available for every event at request time. x-rate-limit-note: Handle HTTP 429 with backoff and avoid tight polling loops. components: schemas: CoverageSource: properties: markets_are_approximate: type: boolean title: Markets Are Approximate default: true lookback_days: type: integer title: Lookback Days type: object required: - lookback_days title: CoverageSource BookmakerCountriesCatalogResponse: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/BookmakerCountryCatalogItem' CoverageLeague: properties: sport: type: string title: Sport league: type: string title: League type: object required: - sport - league title: CoverageLeague RateLimitResponse: allOf: - $ref: '#/components/schemas/ErrorResponse' description: Rate-limit response. Retry after the window or reduce polling frequency. BookmakerCatalogResponse: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/BookmakerCatalogItem' CoverageResponse: properties: as_of: type: string title: As Of bookmakers: items: $ref: '#/components/schemas/CoverageBookmaker' type: array title: Bookmakers sports: items: type: string type: array title: Sports leagues: items: $ref: '#/components/schemas/CoverageLeague' type: array title: Leagues markets: items: $ref: '#/components/schemas/CoverageMarket' type: array title: Markets source: $ref: '#/components/schemas/CoverageSource' type: object required: - as_of - source title: CoverageResponse 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. StringListResponse: type: object required: - items properties: items: type: array items: type: string CoverageMarket: properties: bookmaker: type: string title: Bookmaker sport: type: string title: Sport league: type: string title: League bet_type: type: string title: Bet Type metric: anyOf: - type: string - type: 'null' title: Metric period: anyOf: - type: string - type: 'null' title: Period last_seen_at: type: string title: Last Seen At sample_event_id: type: string title: Sample Event Id type: object required: - bookmaker - sport - league - bet_type - last_seen_at - sample_event_id title: CoverageMarket BookmakerCountryCatalogItem: type: object required: - country_code - bookmakers properties: country_code: type: string example: AU country: type: string nullable: true example: Australia bookmakers: type: array items: type: string description: Canonical bookmaker identifiers active for this country code. BookmakerCatalogItem: type: object required: - bookmaker - country_codes properties: bookmaker: type: string description: Canonical bookmaker identifier accepted by bookmaker filters. example: bet365 country_codes: type: array items: type: string description: Country codes where this bookmaker is active, for example `AU` or `UK`. example: - AU - UK source_bookmaker: type: string nullable: true description: Canonical source bookmaker when this bookmaker reuses another bookmaker's sports odds. example: betnation CoverageBookmaker: properties: bookmaker: type: string title: Bookmaker country_codes: items: type: string type: array title: Country Codes type: object required: - bookmaker title: CoverageBookmaker securitySchemes: ApiKeyAuth: type: apiKey in: header name: X-API-Key description: Send your API key in this header on every request.