{"openapi":"3.1.0","info":{"title":"Odds API","description":"The Odds API V1 provides sports and racing events, bookmaker odds, exchange and prediction-market order books,\nlive price updates, betting opportunity snapshots, and result lookups for production integrations.\n\nAuthenticate every request with `X-API-Key: <your_api_key>` unless an endpoint explicitly states it is public.\nUse `https://api.odds-api.net/v1` as the production base URL.\n\nSnapshot endpoints return JSON and include timestamps or resume tokens when live updates are available.\n`/stream` endpoints use Server-Sent Events (`text/event-stream`) and `/ws` endpoints use WebSockets. Stream\nmessages use the same shape in both transports: an event name such as `delta`, `heartbeat`, or `resync`, plus a\nJSON payload. Store the latest `resume` value and pass it back as `since` after reconnecting.\n\nProduction clients should start with catalog discovery, page event lists with `limit` and `cursor`, fetch odds\nsnapshots for initial state, then use streams for hot events. Recommended starting polling intervals are:\ncatalog metadata every 6-24 hours, sports event lists every 5-15 minutes, racing event lists every 1-5 minutes,\nodds snapshots every 60-120 seconds when streams are not used, betting opportunity snapshots every 30-120 seconds,\nand results every 1-5 minutes after the event starts until settled.\n\nHandle HTTP 429 by honoring `Retry-After` when present, otherwise use jittered exponential backoff. Use\n`/usage` to detect monthly API-credit quota exhaustion and `/limits` to discover response, stream, add-on, and\nplan caps. Most standard API calls use 1 credit; large odds snapshots, history, broad aggregate feeds, and\nsupport-approved betting feeds may use multiple credits as weighted metering rolls out. Cache snapshots by request\nshape, respect `ttl_seconds` when present, display `as_of_ts_ms`, and use `bookmaker_as_of_ts_ms` when a\nper-bookmaker freshness decision matters. `target_refresh_interval_seconds` is a scheduling target, not an\nartificial plan delay or a guarantee that an upstream bookmaker answered on time. Keep the most recent `resume` token with\ncached state. On stream disconnect, reconnect with `since=<last_resume>` and `catchup=true`; on `resync`, reload\nthe snapshot before applying more deltas.\n\nOdds can change quickly. Check event and bookmaker freshness timestamps, handle empty arrays, suspended selections, rate limits, and stale\nprices before displaying or acting on any betting data.\n\nNew odds-api.net API keys default to a compact response shape that keeps essential IDs, prices, teams, markets,\ntimestamps, and resume tokens while omitting raw payloads, source metadata, internal/debug IDs, bookmaker links,\nand non-offered price columns. Existing API keys keep the historical full response shape unless they explicitly\npass the compacting query flags. There is no public `profile` parameter; use the documented per-field flags when\nyou need to opt a compact client back into a specific field group.","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=<snapshot resume>."]}],"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=<last_resume>.","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=<last_resume>.","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."]}},"paths":{"/":{"get":{"tags":["Start here"],"summary":"Start here: API metadata","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiRootResponse"}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Returns the API name, version, OpenAPI document URL, and hosted reference URL.","security":[],"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/status":{"get":{"tags":["Status"],"summary":"API status: Public health summary","operationId":"public_status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicStatusResponse"},"examples":{"default":{"summary":"API status: Public health summary","value":{"status":"operational","as_of":"2026-04-29T10:25:00Z","window_seconds":300,"components":[{"id":"rest_api","name":"REST API","status":"operational","metrics":{"uptime_pct":100.0,"p50_ms":24.0,"p95_ms":410.0,"p99_ms":846.0,"error_rate_pct":0.02}}],"rate_limits":{"status":"operational","throttled_pct":0.4},"source":{"fresh":true,"age_seconds":18}}}}}}},"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 a sanitized public status summary for buyer-facing pages. The response includes recent component availability, latency percentiles, 5xx error rate, stream health, and rate-limit health without exposing internal service names, infrastructure metrics, bookmaker details, raw traffic volume, or incident history.","security":[],"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/me":{"get":{"tags":["Account"],"summary":"Account: Current identity","operationId":"me_me_get","security":[{"ApiKeyAuth":[]}],"parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIdentityResponse"}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Returns the authenticated API identity and enabled product capabilities for the supplied key.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/usage":{"get":{"tags":["Account"],"summary":"Account: Usage","operationId":"usage_usage_get","security":[{"ApiKeyAuth":[]}],"parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"},"examples":{"default":{"summary":"Account: Usage","value":{"period_start_utc":"2026-05-01T00:00:00Z","period_end_utc":"2026-06-01T00:00:00Z","plan":"live","pricing_model":"odds_api_net_v2","api_credits_used":18420,"api_credits_limit":20000000,"stream_hours_used":438.25,"stream_hours_limit":6000,"stream_logical_bytes_used":187654321,"stream_logical_bytes_limit":536870912000,"stream_concurrent_units_used":7,"stream_concurrent_units_limit":25,"exceeded":false}}}}}},"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 quota and usage counters for the supplied API key. New odds-api.net pricing uses API credits rather than raw request counts. Production clients should check this endpoint when 429 responses persist so quota exhaustion does not become an infinite retry loop.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/limits":{"get":{"tags":["Account"],"summary":"Account: Limits","operationId":"limits_limits_get","security":[{"ApiKeyAuth":[]}],"parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitsResponse"},"examples":{"default":{"summary":"Account: Limits","value":{"responses":{"events_limit_max":1000,"odds_snapshot_limit_max":25000,"bets_snapshot_limit_max":20000},"sse":{"heartbeat_sec_min":5,"heartbeat_sec_max":120,"max_batch_default":500},"streams":{"metering":"all authenticated API-key SSE and WebSocket connections","formula":"stream_units * open_seconds / 3600","enforcement_interval_seconds":5}}}}}}},"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 contract-level API-credit, request, stream, add-on, and response limits that clients should respect. Use this to cap page sizes, snapshot sizes, stream heartbeat settings, and stream batch sizes before starting high-volume jobs.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/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."}},"/widgets/odds-ticker":{"get":{"tags":["Widgets"],"summary":"Widgets: Odds ticker","operationId":"odds_ticker_widgets_odds_ticker_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"league","in":"query","required":true,"schema":{"type":"string","minLength":1,"title":"League"},"description":"League filter. Use `/leagues?sport=...` to discover supported values."},{"name":"bookmakers","in":"query","required":true,"schema":{"type":"string","minLength":1,"title":"Bookmakers"},"description":"Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys."},{"name":"widget_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9_.:-]+$","title":"Widget Id"},"description":"Stable widget identifier configured on the API client record."},{"name":"markets","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Markets"},"description":"Comma-separated widget market list. Supported values are moneyline, handicap, and total; aliases include h2h, 1x2, spread, and over_under."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":20,"title":"Limit"},"description":"Maximum items to return. Respect the caps returned by `/limits`."},{"name":"window_hours","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"default":48,"title":"Window Hours"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WidgetOddsTickerResponse"},"examples":{"default":{"summary":"Widgets: Odds ticker","value":{"league":"EPL","widget_id":"homepage-ticker","last_updated":"2026-07-09T01:02:03Z","events":[{"league":"EPL","event_name":"Arsenal vs Chelsea","start_time":1783558800,"last_updated":"2026-07-09T01:02:03Z","markets":[{"market":"moneyline 3w","bookmakers":[{"label":"tab","selections":[{"selection":"home","price":2.2},{"selection":"away","price":2.9},{"selection":"draw","price":3.4}]}]}]}]}}}}}},"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 a small public-safe odds ticker payload for an embeddable website widget. The response is projected from the same Redis-backed main-line sports odds used by the API, but omits event IDs, raw payloads, source metadata, links, no-vig prices, fair odds, debug IDs, and team logo metadata. API keys marked `widgets_only=true` can access this route plus account routes only.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/events":{"get":{"tags":["Sports events"],"summary":"Sports events: Search","operationId":"list_events_events_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."},{"name":"league","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"League"},"description":"League filter. Use `/leagues?sport=...` to discover supported values."},{"name":"start_from","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix seconds; default now.","title":"Start From"},"description":"Unix seconds lower bound for event start time. Use bounded windows in production polling."},{"name":"start_to","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix seconds; optional.","title":"Start To"},"description":"Unix seconds upper bound for event start time. Keep windows narrow for hot sync jobs."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pagination cursor: 'start_time:event_id'.","title":"Cursor"},"description":"Pagination cursor from the previous `next_cursor`. Keep filters identical between pages."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"},"description":"Maximum items to return. Respect the caps returned by `/limits`."},{"name":"include_bookmaker_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Bookmaker Ids"},"description":"When true, include bookmaker-to-odds-data IDs and preview ID maps on event responses."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_opportunity_counts","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Opportunity Counts"}},{"name":"not_started_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Not Started Only"}},{"name":"not_started_buffer_seconds","in":"query","required":false,"schema":{"type":"integer","default":120,"title":"Not Started Buffer Seconds"}},{"name":"event_states","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Lifecycle filter. Requesting in_play automatically applies the live lookback window.","title":"Event States"},"description":"Lifecycle filter. Requesting in_play automatically applies the live lookback window."},{"name":"live_candidates","in":"query","required":false,"schema":{"type":"boolean","description":"Include already-started events that remain candidates for live play; this is not confirmation of current play.","default":false,"title":"Live Candidates"},"description":"Include already-started events that remain candidates for live play; this is not confirmation of current play."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventListResponse"},"examples":{"default":{"summary":"Sports events: Search","value":{"items":[{"event_id":"3704597661","sport":"rugby-league","league":"NRL","start_time":1760000000,"home_team":"Home","away_team":"Away","bookmakers":{"bet365":"odds-doc-id"}}],"next_cursor":null,"count":1}}}}}},"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":"Searches sports events by sport, league, team, time window, status, bookmaker coverage, and pagination cursor. For production polling, use bounded time windows, keep filters stable across pages, and pass `next_cursor` back as `cursor` until there is no next cursor.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/events/live":{"get":{"tags":["Sports events"],"summary":"GET events live","description":"Public Odds API operation. Authenticate with `X-API-Key`. Check timestamps before displaying prices and handle empty, stale, or suspended markets.","operationId":"list_live_event_candidates_events_live_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."},{"name":"league","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"League"},"description":"League filter. Use `/leagues?sport=...` to discover supported values."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pagination cursor: 'start_time:event_id'.","title":"Cursor"},"description":"Pagination cursor from the previous `next_cursor`. Keep filters identical between pages."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"},"description":"Maximum items to return. Respect the caps returned by `/limits`."},{"name":"include_bookmaker_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Bookmaker Ids"},"description":"When true, include bookmaker-to-odds-data IDs and preview ID maps on event responses."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_opportunity_counts","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Opportunity Counts"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventListResponse"}}}},"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"}}}}},"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/events/{event_id}":{"get":{"tags":["Sports events"],"summary":"Sports events: Event details","operationId":"get_event_events__event_id__get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"include_links","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Links"},"description":"When true, include bookmaker/deep-link fields such as match links and racing links."},{"name":"include_raw_payload","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Raw Payload"},"description":"When true, include raw stored payload/data objects where the endpoint exposes them."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_bookmaker_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Bookmaker Ids"},"description":"When true, include bookmaker-to-odds-data IDs and preview ID maps on event responses."},{"name":"include_debug_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Debug Ids"},"description":"When true, include internal/subgroup/opposing IDs useful for reconciliation."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventDetail"}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Returns the current event record for a canonical sports event ID.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/events/{event_id}/bookmakers":{"get":{"tags":["Sports events"],"summary":"Sports events: Bookmaker coverage","operationId":"event_bookmakers_events__event_id__bookmakers_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookmakersResponse"}}}},"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 bookmakers currently attached to a sports event.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/events/{event_id}/odds/snapshot":{"get":{"tags":["Sports odds"],"summary":"Event odds: Snapshot","operationId":"odds_snapshot_events__event_id__odds_snapshot_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":25000,"minimum":1,"description":"Soft item target when bookmakers is omitted. Pages contain whole bookmakers and may exceed this target. When bookmakers is supplied, all matching lines are returned up to the response safety cap.","default":2000,"title":"Limit"},"description":"Soft item target when `bookmakers` is omitted. Pages contain whole bookmakers and may exceed this target. With an explicit bookmaker filter, all matching lines are returned up to the 25,000-item safety cap."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque bookmaker-page cursor from next_cursor. Use only when bookmakers is omitted and keep all filters identical between pages.","title":"Cursor"},"description":"Opaque bookmaker-page cursor from the previous `next_cursor`. Use only when `bookmakers` is omitted and keep all filters identical between pages."},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker allow-list. Explicitly requested bookmakers are returned complete in one response, up to the response safety cap.","title":"Bookmakers"},"description":"Comma-separated bookmaker allow-list. Explicitly requested bookmakers are returned complete in one response, up to the 25,000-item safety cap. Use `/bookmakers` to discover supported keys."},{"name":"types","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated type allow-list","title":"Types"},"description":"Comma-separated market type allow-list for odds filters."},{"name":"market_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated market_key allow-list","title":"Market Keys"},"description":"Comma-separated market key allow-list for odds filters."},{"name":"periods","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated periods allow-list","title":"Periods"},"description":"Comma-separated period allow-list for odds filters."},{"name":"price_fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Fields"},"description":"`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_debug_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Debug Ids"},"description":"When true, include internal/subgroup/opposing IDs useful for reconciliation."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"When true, include unavailable or suspended rows where the endpoint supports them."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OddsSnapshotResponse"},"examples":{"default":{"summary":"Event odds: Snapshot","value":{"event_id":"3704597661","as_of_ts_ms":1760000000000,"snapshot_capture_ts_ms":1759999999800,"bookmaker_as_of_ts_ms":{"bet365":1760000000000},"oldest_bookmaker_as_of_ts_ms":1760000000000,"target_refresh_interval_seconds":60,"ttl_seconds":1800,"items":[{"id":"bet365::moneyline::moneyline::0::::home::","event_id":"3704597661","bookmaker":"bet365","market_key":"moneyline","bet_type":"moneyline","period":"full time","side":"home","selection_name":"Home","odds":2.1,"fair_odds":1.98,"is_available":true}],"next_cursor":null,"complete":true,"bookmakers_included":["bet365"],"bookmaker_counts":{"bet365":1},"resume":"1760000000000-0"}}}}}},"413":{"description":"The requested bookmaker-complete snapshot exceeds the response safety cap."},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Returns the current odds lines for one event. Use filters to narrow bookmakers, market types, market keys, and periods. An explicit `bookmakers` filter returns every matching line for those bookmakers in one response, up to the 25,000-item safety cap. Without a bookmaker filter, pages contain whole bookmakers, so a bookmaker's matching markets are never split across pages. Follow the opaque `next_cursor` until `complete=true`. Cache by request shape. `as_of_ts_ms` is when the API accepted the latest successful event snapshot or authoritative bookmaker subset; use `bookmaker_as_of_ts_ms` for bookmaker-specific freshness and compare it with `target_refresh_interval_seconds`. Respect `ttl_seconds` when present, and persist `resume` when you plan to subscribe to updates. Pass `price_fields=odds,fair` to include nullable composite fair odds alongside bookmaker odds. Exchange orderbooks are excluded from this sportsbook-style odds surface.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/events/{event_id}/odds/stream":{"get":{"tags":["Sports odds"],"summary":"Event odds: Stream (SSE)","operationId":"odds_stream_events__event_id__odds_stream_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker allow-list","title":"Bookmakers"},"description":"Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys."},{"name":"types","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated type allow-list","title":"Types"},"description":"Comma-separated market type allow-list for odds filters."},{"name":"market_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated market_key allow-list","title":"Market Keys"},"description":"Comma-separated market key allow-list for odds filters."},{"name":"periods","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated periods allow-list","title":"Periods"},"description":"Comma-separated period allow-list for odds filters."},{"name":"price_fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Fields"},"description":"`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_debug_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Debug Ids"},"description":"When true, include internal/subgroup/opposing IDs useful for reconciliation."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"When true, include unavailable or suspended rows where the endpoint supports them."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Redis stream ID","title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."},{"name":"max_batch","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Max Batch"},"description":"Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients."}],"responses":{"200":{"description":"Server-Sent Events stream. Each message has an event name and JSON data payload.","content":{"text/event-stream":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/OddsStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Decoded delta message","value":{"event":"delta","data":{"event_id":"3704597661","resume":"1760000000000-0","snapshot_id":"capture-123:3704597661","batch_index":1,"batch_count":1,"changes":[]}}},"heartbeat":{"summary":"Decoded heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Decoded resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Server-Sent Events feed for odds changes on one event. Subscribe after reading the snapshot and pass the snapshot `resume` value as `since` to receive catch-up changes when available. Handle ordered semantic `delta` batches idempotently and persist each batch's `resume`; a `heartbeat` carries current freshness even when prices did not change. Reload the snapshot after `resync`. Exchange orderbook changes are served only from the exchange orderbook stream.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/events/{event_id}/odds/ws":{"get":{"operationId":"websocket_events_event_id_odds_ws","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker allow-list","title":"Bookmakers"},"description":"Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys."},{"name":"types","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated type allow-list","title":"Types"},"description":"Comma-separated market type allow-list for odds filters."},{"name":"market_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated market_key allow-list","title":"Market Keys"},"description":"Comma-separated market key allow-list for odds filters."},{"name":"periods","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated periods allow-list","title":"Periods"},"description":"Comma-separated period allow-list for odds filters."},{"name":"price_fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Fields"},"description":"`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_debug_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Debug Ids"},"description":"When true, include internal/subgroup/opposing IDs useful for reconciliation."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"When true, include unavailable or suspended rows where the endpoint supports them."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Redis stream ID","title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."},{"name":"max_batch","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Max Batch"},"description":"Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients."}],"responses":{"101":{"description":"WebSocket connection established. Messages are JSON objects.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/OddsStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"event_id":"3704597661","resume":"1760000000000-0","snapshot_id":"capture-123:3704597661","batch_index":1,"batch_count":1,"changes":[]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"OpenAPI tooling compatibility response schema. Runtime WebSocket connections upgrade with 101.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/OddsStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"event_id":"3704597661","resume":"1760000000000-0","snapshot_id":"capture-123:3704597661","batch_index":1,"batch_count":1,"changes":[]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}}},"x-websocket":true,"x-stream-equivalent":"/events/{event_id}/odds/stream","tags":["Sports odds"],"summary":"Event odds: Stream (WebSocket)","description":"WebSocket feed for odds changes on one event. Messages use the same `delta`, `heartbeat`, and `resync` payloads as the SSE stream. Reconnect with jittered exponential backoff and `since=<last_resume>`.","security":[{"ApiKeyAuth":[]}],"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/events/{event_id}/odds/history":{"get":{"tags":["Sports odds"],"summary":"Event odds history: Snapshot","operationId":"odds_history_events__event_id__odds_history_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"selection_key","in":"query","required":true,"schema":{"type":"string","minLength":1,"title":"Selection Key"},"description":"Stable selection identifier from an odds snapshot line, used for history and line movement."},{"name":"market_group_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Group Id"},"description":"Optional market grouping filter for history queries."},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker allow-list","title":"Bookmakers"},"description":"Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys."},{"name":"from_ts","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO8601 UTC start","title":"From Ts"},"description":"ISO8601 UTC start timestamp for a bounded history query."},{"name":"to_ts","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO8601 UTC end","title":"To Ts"},"description":"ISO8601 UTC end timestamp for a bounded history query."},{"name":"price_type","in":"query","required":false,"schema":{"type":"string","pattern":"^(odds|odds_no_vig|fair_odds)$","default":"odds","title":"Price Type"},"description":"History price type to return, for example odds."},{"name":"price_fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Fields"},"description":"`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_debug_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Debug Ids"},"description":"When true, include internal/subgroup/opposing IDs useful for reconciliation."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"When true, include unavailable or suspended rows where the endpoint supports them."},{"name":"limit_points_per_bookmaker","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":1,"default":2000,"title":"Limit Points Per Bookmaker"},"description":"Maximum history points per bookmaker. Use this to keep chart/backtest payloads bounded."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OddsHistoryResponse"},"examples":{"default":{"summary":"Event odds history: Snapshot","value":{"event_id":"3704597661","selection_key":"moneyline:home","price_type":"odds","series":[{"bookmaker_name":"bet365","points":[{"tick_ts":"2026-04-29T08:00:00Z","is_available":true,"odds":2.08},{"tick_ts":"2026-04-29T08:05:00Z","is_available":true,"odds":2.1}]}],"meta":{"from_ts":"2026-04-29T08:00:00Z","to_ts":"2026-04-29T09:00:00Z","available_price_types":["odds","odds_no_vig","fair_odds"]}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Returns line movement for a single selection across bookmakers and a time range. Use `selection_key` from an odds snapshot response, bound queries with `from_ts` and `to_ts`, and narrow by bookmaker or market when building charts or backtests.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/events/{event_id}/odds/history/stream":{"get":{"tags":["Sports odds"],"summary":"Event odds history: Stream (SSE)","operationId":"odds_history_stream_events__event_id__odds_history_stream_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"selection_key","in":"query","required":true,"schema":{"type":"string","minLength":1,"title":"Selection Key"},"description":"Stable selection identifier from an odds snapshot line, used for history and line movement."},{"name":"market_group_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Group Id"},"description":"Optional market grouping filter for history queries."},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker allow-list","title":"Bookmakers"},"description":"Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys."},{"name":"price_type","in":"query","required":false,"schema":{"type":"string","pattern":"^(odds|odds_no_vig|fair_odds)$","default":"odds_no_vig","title":"Price Type"},"description":"History price type to return, for example odds."},{"name":"price_fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Fields"},"description":"`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_debug_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Debug Ids"},"description":"When true, include internal/subgroup/opposing IDs useful for reconciliation."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"When true, include unavailable or suspended rows where the endpoint supports them."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Redis stream ID","title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."},{"name":"max_batch","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Max Batch"},"description":"Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients."}],"responses":{"200":{"description":"Server-Sent Events stream. Each message has an event name and JSON data payload.","content":{"text/event-stream":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/OddsHistoryStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Decoded delta message","value":{"event":"delta","data":{"event_id":"3704597661","selection_key":"moneyline:home","resume":"1760000000000-0","points":[]}}},"heartbeat":{"summary":"Decoded heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Decoded resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Server-Sent Events feed for line movement on one selection. This is useful for charts that should update while an event market is moving.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/events/{event_id}/odds/history/ws":{"get":{"operationId":"websocket_events_event_id_odds_history_ws","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"selection_key","in":"query","required":true,"schema":{"type":"string","minLength":1,"title":"Selection Key"},"description":"Stable selection identifier from an odds snapshot line, used for history and line movement."},{"name":"market_group_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Group Id"},"description":"Optional market grouping filter for history queries."},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker allow-list","title":"Bookmakers"},"description":"Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys."},{"name":"price_type","in":"query","required":false,"schema":{"type":"string","pattern":"^(odds|odds_no_vig|fair_odds)$","default":"odds_no_vig","title":"Price Type"},"description":"History price type to return, for example odds."},{"name":"price_fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Fields"},"description":"`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_debug_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Debug Ids"},"description":"When true, include internal/subgroup/opposing IDs useful for reconciliation."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"When true, include unavailable or suspended rows where the endpoint supports them."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Redis stream ID","title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."},{"name":"max_batch","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Max Batch"},"description":"Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients."}],"responses":{"101":{"description":"WebSocket connection established. Messages are JSON objects.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/OddsHistoryStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"event_id":"3704597661","selection_key":"moneyline:home","resume":"1760000000000-0","points":[]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"OpenAPI tooling compatibility response schema. Runtime WebSocket connections upgrade with 101.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/OddsHistoryStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"event_id":"3704597661","selection_key":"moneyline:home","resume":"1760000000000-0","points":[]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}}},"x-websocket":true,"x-stream-equivalent":"/events/{event_id}/odds/history/stream","tags":["Sports odds"],"summary":"Event odds history: Stream (WebSocket)","description":"WebSocket feed for line movement on one selection. Messages mirror the history SSE stream payloads.","security":[{"ApiKeyAuth":[]}],"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/events/{event_id}/exchange/orderbook/snapshot":{"get":{"tags":["Sports exchange"],"summary":"Event exchange order book: Snapshot","operationId":"exchange_orderbook_snapshot_events__event_id__exchange_orderbook_snapshot_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"exchanges","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated exchange filter: betdaq, betfair, smarkets, matchbook.","title":"Exchanges"},"description":"Comma-separated exchange allow-list. Supported values are `betdaq`, `betfair`, `smarkets`, and `matchbook`."},{"name":"market_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated market_key allow-list.","title":"Market Keys"},"description":"Comma-separated market key allow-list for odds filters."},{"name":"selection_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated selection_key allow-list.","title":"Selection Keys"},"description":"Comma-separated stable selection identifiers from an exchange order book or odds snapshot."},{"name":"depth","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"description":"Price levels per back/lay side.","default":3,"title":"Depth"},"description":"Number of exchange price levels per back/lay side. Use smaller values for lower latency and payload size."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"When true, include unavailable or suspended rows where the endpoint supports them."},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Request bounded, demand-driven Betfair collection before returning.","default":false,"title":"Refresh"},"description":"Request bounded, demand-driven Betfair collection before returning."},{"name":"refresh_timeout_seconds","in":"query","required":false,"schema":{"type":"number","maximum":10.0,"minimum":0.5,"default":5.0,"title":"Refresh Timeout Seconds"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExchangeOrderBookSnapshotResponse"},"examples":{"default":{"summary":"Event exchange order book: Snapshot","value":{"event_id":"3704597661","as_of_ts_ms":1760000000000,"ttl_seconds":30,"items":[{"id":"betfair::1.23456789::moneyline","event_id":"3704597661","exchange":"betfair","exchange_market_id":"1.23456789","market_key":"moneyline","market_name":"Match Odds","bet_type":"moneyline","period":"full time","home_team":"Home","away_team":"Away","status":"open","in_play":false,"total_matched":24567.12,"total_available":204.8,"total_available_source":"displayed_ladders","currency":"AUD","observed_at":"2026-08-22T08:00:00Z","selections":[{"selection_key":"moneyline:home","exchange_selection_id":"12345","selection_name":"Home","last_traded_price":2.08,"traded_volume_by_price":[{"price":2.08,"size":300.0}],"available_to_back":[{"price":2.08,"size":120.5}],"available_to_lay":[{"price":2.1,"size":84.3}],"best_back_price":2.08,"best_back_size":120.5,"best_lay_price":2.1,"best_lay_size":84.3}]}],"next_cursor":null,"resume":"1760000000000-0"}}}}}},"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 sports betting exchange order books for one event. Supported exchanges are betdaq, betfair, smarkets, and matchbook. Each selection includes back and lay price levels with available size, plus top-of-book summary fields such as best_back_price and best_lay_price. Market identity, team identity, source and observation timestamps, currency, total matched, total available, traded volume, and traded volume by price are retained when supplied by the exchange source. Smarkets volume is reported in GBP and includes its native double_stake_volume when available. Use `depth` to limit executable ladder levels and cache only briefly because exchange liquidity can move quickly.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/events/{event_id}/exchange/orderbook/stream":{"get":{"tags":["Sports exchange"],"summary":"Event exchange order book: Stream (SSE)","operationId":"exchange_orderbook_stream_events__event_id__exchange_orderbook_stream_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"exchanges","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated exchange filter.","title":"Exchanges"},"description":"Comma-separated exchange allow-list. Supported values are `betdaq`, `betfair`, `smarkets`, and `matchbook`."},{"name":"market_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated market_key allow-list.","title":"Market Keys"},"description":"Comma-separated market key allow-list for odds filters."},{"name":"selection_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated selection_key allow-list.","title":"Selection Keys"},"description":"Comma-separated stable selection identifiers from an exchange order book or odds snapshot."},{"name":"depth","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"default":3,"title":"Depth"},"description":"Number of exchange price levels per back/lay side. Use smaller values for lower latency and payload size."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"When true, include unavailable or suspended rows where the endpoint supports them."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Redis stream ID","title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."},{"name":"max_batch","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Max Batch"},"description":"Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients."}],"responses":{"200":{"description":"Server-Sent Events stream. Each message has an event name and JSON data payload.","content":{"text/event-stream":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/ExchangeOrderBookStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Decoded delta message","value":{"event":"delta","data":{"event_id":"3704597661","resume":"1760000000000-0","changes":[]}}},"heartbeat":{"summary":"Decoded heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Decoded resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Server-Sent Events feed for sports exchange order book changes on one event. Subscribe after reading the snapshot and pass the snapshot `resume` value as `since` to receive catch-up changes when available. Handle `delta`, `heartbeat`, and `resync`; reload the snapshot after `resync`.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/events/{event_id}/exchange/orderbook/ws":{"get":{"operationId":"websocket_events_event_id_exchange_orderbook_ws","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"exchanges","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated exchange filter.","title":"Exchanges"},"description":"Comma-separated exchange allow-list. Supported values are `betdaq`, `betfair`, `smarkets`, and `matchbook`."},{"name":"market_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated market_key allow-list.","title":"Market Keys"},"description":"Comma-separated market key allow-list for odds filters."},{"name":"selection_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated selection_key allow-list.","title":"Selection Keys"},"description":"Comma-separated stable selection identifiers from an exchange order book or odds snapshot."},{"name":"depth","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"default":3,"title":"Depth"},"description":"Number of exchange price levels per back/lay side. Use smaller values for lower latency and payload size."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"When true, include unavailable or suspended rows where the endpoint supports them."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Redis stream ID","title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."},{"name":"max_batch","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Max Batch"},"description":"Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients."}],"responses":{"101":{"description":"WebSocket connection established. Messages are JSON objects.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/ExchangeOrderBookStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"event_id":"3704597661","resume":"1760000000000-0","changes":[]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"OpenAPI tooling compatibility response schema. Runtime WebSocket connections upgrade with 101.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/ExchangeOrderBookStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"event_id":"3704597661","resume":"1760000000000-0","changes":[]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}}},"x-websocket":true,"x-stream-equivalent":"/events/{event_id}/exchange/orderbook/stream","tags":["Sports exchange"],"summary":"Event exchange order book: Stream (WebSocket)","description":"WebSocket feed for sports exchange order book changes on one event. Messages mirror the exchange order book SSE stream payloads.","security":[{"ApiKeyAuth":[]}],"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/events/{event_id}/exchange/markets":{"get":{"tags":["Sports exchange"],"summary":"In-play exchange: Discover markets","description":"Lists available Betfair markets across sports, including soccer and cricket. Supply a WagerWise event ID or id_type=betfair with a native Betfair event ID. Filter using market_types; MATCH_ODDS is sorted first. Use the returned opaque market_id values with the multiplexed WebSocket; do not send Betfair market names or IDs.","operationId":"exchange_markets_events__event_id__exchange_markets_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"exchange","in":"query","required":false,"schema":{"type":"string","pattern":"^betfair$","default":"betfair","title":"Exchange"}},{"name":"id_type","in":"query","required":false,"schema":{"type":"string","pattern":"^(wagerwise|betfair)$","description":"Namespace of event_id; Betfair IDs are numeric event IDs, not market IDs.","default":"wagerwise","title":"Id Type"},"description":"Namespace of event_id; Betfair IDs are numeric event IDs, not market IDs."},{"name":"market_types","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"description":"Optional comma-separated Betfair market types, e.g. MATCH_ODDS,OVER_UNDER_25.","title":"Market Types"},"description":"Optional comma-separated Betfair market types, e.g. MATCH_ODDS,OVER_UNDER_25."},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Refresh"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExchangeMarketsResponse"}}}},"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"}}}}},"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/exchange/betfair/events/{betfair_event_id}/markets":{"get":{"tags":["Sports exchange"],"summary":"In-play exchange: Discover by Betfair event ID","description":"Public Odds API operation. Authenticate with `X-API-Key`. Check timestamps before displaying prices and handle empty, stale, or suspended markets.","operationId":"betfair_event_markets_exchange_betfair_events__betfair_event_id__markets_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"betfair_event_id","in":"path","required":true,"schema":{"type":"string","title":"Betfair Event Id"}},{"name":"market_types","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Market Types"}},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Refresh"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExchangeMarketsResponse"}}}},"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"}}}}},"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/exchange/orderbooks/ws":{"get":{"operationId":"websocket_exchange_orderbooks_ws","parameters":[],"responses":{"101":{"description":"WebSocket connection established. Send subscribe/unsubscribe JSON commands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InPlayExchangeWebSocketMessage"}}}},"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"}}}}},"x-websocket":true,"x-subscription-command":{"op":"subscribe","market_ids":["bfm_..."],"depth":3},"tags":["Sports exchange"],"summary":"In-play exchange: Multiplexed WebSocket","description":"One support-approved WebSocket can subscribe and unsubscribe dynamically from up to five opaque market IDs. Send {op: subscribe, market_ids: [...], depth: 3}; the server acknowledges immediately, sends a cached image when available, then deltas and heartbeats. Every order book includes in_play. The handshake uses one API credit; connection time and logical bytes use the plan's stream quotas.","security":[{"ApiKeyAuth":[]}],"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/exchange/tennis/scores/ws":{"get":{"operationId":"websocket_exchange_tennis_scores","x-websocket":true,"x-subscription-command":{"op":"subscribe","event_ids":["betfair:123"]},"responses":{"101":{"description":"WebSocket established; subscribe to discovered tennis event IDs.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/TennisScoreMessage"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["subscribed","unsubscribed","heartbeat","pong","error","warming_timeout","score_unavailable","stale","upstream_unavailable"]},"event_id":{"type":"string"},"event_ids":{"type":"array","items":{"type":"string"}},"active_event_ids":{"type":"array","items":{"type":"string"}},"warming":{"type":"array","items":{"type":"string"}},"code":{"type":"string"},"retryable":{"type":"boolean"},"ts":{"type":"integer"}}}]}}}},"403":{"description":"Tennis scores entitlement required."},"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"}}}},"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"}}}}},"tags":["Sports exchange"],"summary":"Live tennis scores: Multiplexed WebSocket","description":"Requires X-API-Key with tennis_scores_enabled=true. Discover tennis event IDs first using exchange market discovery. Send subscribe/unsubscribe commands with event_ids (maximum 10), or ping. Opening alone starts no collection. The first score is an image; delta contains a complete replacement score. Five-second heartbeats indicate socket liveness only. Scores are ephemeral: no history or replay. received_at is our receipt time; source_timestamp is null. Scores and prices are independent observations and may be delayed or inaccurate. Nullable fields are not inferred. Handle unknown_event_ids, event_limit_exceeded, warming_timeout, score_unavailable, stale and upstream_unavailable. Quota exhaustion closes with 4429; authentication and availability use the standard stream close codes. Reconnect and subscribe again after disconnect; sequence numbers are scoped to the active router process.","security":[{"ApiKeyAuth":[]}],"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/events/{event_id}/prediction-markets/orderbook/snapshot":{"get":{"tags":["Prediction markets"],"summary":"Prediction-market order book: Snapshot","operationId":"prediction_orderbook_snapshot_events__event_id__prediction_markets_orderbook_snapshot_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"providers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated provider filter: kalshi, polymarket.","title":"Providers"},"description":"Comma-separated prediction-market provider allow-list: `polymarket`, `kalshi`."},{"name":"market_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Keys"},"description":"Comma-separated market key allow-list for odds filters."},{"name":"contract_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contract Ids"},"description":"Comma-separated contract ID allow-list from a prediction-market snapshot."},{"name":"depth","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"default":3,"title":"Depth"},"description":"Number of probability price levels per bid/ask side. Use smaller values for lower latency and payload size."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"When true, include unavailable or suspended rows where the endpoint supports them."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PredictionOrderBookSnapshotResponse"},"examples":{"default":{"summary":"Prediction-market order book: Snapshot","value":{"event_id":"3704597661","as_of_ts_ms":1760000000000,"ttl_seconds":30,"items":[{"id":"polymarket::nba-example::moneyline","event_id":"3704597661","provider":"polymarket","provider_market_id":"nba-example","market_key":"moneyline","market_name":"Home vs Away","bet_type":"moneyline","period":"full time","home_team":"Home","away_team":"Away","status":"open","currency":"USD","size_unit":"contracts","total_liquidity":4200.0,"fee_model":"polymarket_sports_taker","fee_estimated":true,"observed_at":"2026-08-26T08:00:00Z","contracts":[{"contract_id":"home-contract","contract_name":"Home","outcome":"yes","side":"home","status":"open","probability_bids":[{"price":0.51,"size":120.0}],"probability_asks":[{"price":0.52,"size":95.0}],"best_bid_probability":0.51,"best_bid_size":120.0,"best_ask_probability":0.52,"best_ask_size":95.0,"gross_decimal_odds":1.92307692,"fee_adjusted_decimal_odds":1.88,"projection_eligible":true}]}],"next_cursor":null,"resume":"1760000000000-0"}}}}}},"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 Polymarket and Kalshi order books linked to one canonical WagerWise sports event. Each contract contains executable probability bid and ask ladders, top-of-book probability and size, the most recent trade probability when available, gross and estimated fee-adjusted decimal odds, liquidity metadata, and source freshness. Use `providers`, `market_keys`, and `contract_ids` to narrow the response and `depth` to bound each ladder. Market availability is curated to WagerWise's supported sports offering.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/events/{event_id}/prediction-markets/orderbook/stream":{"get":{"tags":["Prediction markets"],"summary":"Prediction-market order book: Stream (SSE)","operationId":"prediction_orderbook_stream_events__event_id__prediction_markets_orderbook_stream_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"providers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Providers"},"description":"Comma-separated prediction-market provider allow-list: `polymarket`, `kalshi`."},{"name":"market_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Keys"},"description":"Comma-separated market key allow-list for odds filters."},{"name":"contract_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contract Ids"},"description":"Comma-separated contract ID allow-list from a prediction-market snapshot."},{"name":"depth","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"default":3,"title":"Depth"},"description":"Number of probability price levels per bid/ask side. Use smaller values for lower latency and payload size."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"When true, include unavailable or suspended rows where the endpoint supports them."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."},{"name":"max_batch","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Max Batch"},"description":"Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients."}],"responses":{"200":{"description":"Server-Sent Events stream. Each message has an event name and JSON data payload.","content":{"text/event-stream":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/PredictionOrderBookStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Decoded delta message","value":{"event":"delta","data":{"event_id":"3704597661","resume":"1760000000000-0","changes":[]}}},"heartbeat":{"summary":"Decoded heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Decoded resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Server-Sent Events feed for prediction-market order-book changes on one event. Read the snapshot first, then reconnect with its `resume` token as `since`. Handle `delta`, `heartbeat`, and `resync`; reload the snapshot after `resync`.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/events/{event_id}/prediction-markets/orderbook/ws":{"get":{"operationId":"websocket_events_event_id_prediction_markets_orderbook_ws","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"providers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Providers"},"description":"Comma-separated prediction-market provider allow-list: `polymarket`, `kalshi`."},{"name":"market_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Keys"},"description":"Comma-separated market key allow-list for odds filters."},{"name":"contract_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contract Ids"},"description":"Comma-separated contract ID allow-list from a prediction-market snapshot."},{"name":"depth","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"default":3,"title":"Depth"},"description":"Number of probability price levels per bid/ask side. Use smaller values for lower latency and payload size."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"When true, include unavailable or suspended rows where the endpoint supports them."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."},{"name":"max_batch","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Max Batch"},"description":"Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients."}],"responses":{"101":{"description":"WebSocket connection established. Messages are JSON objects.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/PredictionOrderBookStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"event_id":"3704597661","resume":"1760000000000-0","changes":[]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"OpenAPI tooling compatibility response schema. Runtime WebSocket connections upgrade with 101.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/PredictionOrderBookStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"event_id":"3704597661","resume":"1760000000000-0","changes":[]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}}},"x-websocket":true,"x-stream-equivalent":"/events/{event_id}/prediction-markets/orderbook/stream","tags":["Prediction markets"],"summary":"Prediction-market order book: Stream (WebSocket)","description":"WebSocket feed for prediction-market order-book changes on one event. Messages mirror the prediction-market SSE stream payloads and use the same filters and resume semantics.","security":[{"ApiKeyAuth":[]}],"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/racing/events":{"get":{"tags":["Racing events"],"summary":"Racing events: Search","operationId":"list_racing_events_racing_events_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"race_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated canonical racing types: `horse-racing`, `greyhound-racing`, or `harness-racing`. Legacy shorthand is normalized for backward compatibility.","title":"Race Type"},"description":"Comma-separated canonical racing type filters: `horse-racing`, `greyhound-racing`, or `harness-racing`. Legacy aliases such as `horse`, `thoroughbred`, `greyhound`, `dog`, `dogs`, and `harness` are normalized for backward compatibility."},{"name":"race_state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated race states.","title":"Race State"},"description":"Comma-separated racing state filters."},{"name":"race_country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated racing country codes: `AU`, `NZ`, `GB`, or `IE`. Returned combinations depend on the live racing schedule.","title":"Race Country"},"description":"Comma-separated racing country filters: `AU`, `NZ`, `GB`, or `IE`. Returned combinations depend on the live racing schedule."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated statuses.","title":"Status"},"description":"Comma-separated event lifecycle statuses. Omit for early discovery: fetching alone excludes open races that may already have prices. This is not the bookmaker odds snapshot status."},{"name":"start_from","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix seconds; default now-900.","title":"Start From"},"description":"Unix seconds lower bound for event start time. Use bounded windows in production polling."},{"name":"start_to","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix seconds; optional.","title":"Start To"},"description":"Unix seconds upper bound for event start time. Keep windows narrow for hot sync jobs."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pagination cursor: 'race_start_time:event_id'.","title":"Cursor"},"description":"Pagination cursor from the previous `next_cursor`. Keep filters identical between pages."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"},"description":"Maximum items to return. Respect the caps returned by `/limits`."},{"name":"include_links","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Links"},"description":"When true, include bookmaker/deep-link fields such as match links and racing links."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RacingEventListResponse"},"examples":{"default":{"summary":"Racing events: Search","value":{"items":[{"event_id":"race-1001","race_type":"horse-racing","race_country":"AU","race_state":"QLD","status":"open","race_start_time":1760000000,"race_venue":"Doomben","active_runners":7,"total_runners":8,"scratched_runners":1,"runner_count_source":"betfair","runner_count_updated_at_ts":1759999700,"runner_count_complete":true}],"next_cursor":null,"count":1}}}}}},"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":"Searches upcoming and live horse, greyhound, and harness racing events across Australia (`AU`), New Zealand (`NZ`), Great Britain (`GB`), and Ireland (`IE`). Use canonical race types `horse-racing`, `greyhound-racing`, and `harness-racing`; legacy shorthand is normalized for backward compatibility. Returned combinations depend on the live schedule. Racing polling should use narrow time windows, stable cursors, and shorter intervals near jump. Omit status to discover early races: status=fetching excludes events still marked open, even when early odds exist. Event lifecycle status is distinct from each bookmaker odds snapshot status. Runner counts prefer Betfair's complete scratch-aware field, fall back to another bookmaker or the schedule, and expose source, timestamp, and completeness metadata. Racing odds are not filtered by the API key's betting-opportunity bookmaker selection.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/racing/events/stream":{"get":{"tags":["Racing events"],"summary":"Racing events: Stream (SSE)","operationId":"stream_racing_events_racing_events_stream_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"include_links","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Links"},"description":"When true, include bookmaker/deep-link fields such as match links and racing links."},{"name":"include_raw_payload","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Raw Payload"},"description":"When true, include raw stored payload/data objects where the endpoint exposes them."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Redis stream ID","title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."},{"name":"max_batch","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Max Batch"},"description":"Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients."}],"responses":{"200":{"description":"Server-Sent Events stream. Each message has an event name and JSON data payload.","content":{"text/event-stream":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/RacingEventsStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Decoded delta message","value":{"event":"delta","data":{"resume":"1760000000000-0","changes":[]}}},"heartbeat":{"summary":"Decoded heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Decoded resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Server-Sent Events feed for racing event inserts, updates, and removals. Store resume tokens and reload the event list if a stream asks the client to resync.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":2,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/racing/events/ws":{"get":{"operationId":"websocket_racing_events_ws","parameters":[{"name":"include_links","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Links"},"description":"When true, include bookmaker/deep-link fields such as match links and racing links."},{"name":"include_raw_payload","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Raw Payload"},"description":"When true, include raw stored payload/data objects where the endpoint exposes them."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Redis stream ID","title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."},{"name":"max_batch","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Max Batch"},"description":"Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients."}],"responses":{"101":{"description":"WebSocket connection established. Messages are JSON objects.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/RacingEventsStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"resume":"1760000000000-0","changes":[]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"OpenAPI tooling compatibility response schema. Runtime WebSocket connections upgrade with 101.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/RacingEventsStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"resume":"1760000000000-0","changes":[]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}}},"x-websocket":true,"x-stream-equivalent":"/racing/events/stream","tags":["Racing events"],"summary":"Racing events: Stream (WebSocket)","description":"WebSocket feed for racing event inserts, updates, and removals. Messages mirror the racing events SSE feed.","security":[{"ApiKeyAuth":[]}],"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":2,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/racing/events/{event_id}":{"get":{"tags":["Racing events"],"summary":"Racing events: Event details","operationId":"get_racing_event_racing_events__event_id__get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"include_links","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Links"},"description":"When true, include bookmaker/deep-link fields such as match links and racing links."},{"name":"include_raw_payload","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Raw Payload"},"description":"When true, include raw stored payload/data objects where the endpoint exposes them."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RacingEventDetail"}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Returns the current racing event record for a canonical race ID.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/racing/events/{event_id}/odds":{"get":{"tags":["Racing odds"],"summary":"Racing odds: Snapshot","operationId":"get_racing_odds_snapshot_racing_events__event_id__odds_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker filter.","title":"Bookmakers"},"description":"Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys."},{"name":"include_links","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Links"},"description":"Include bookmaker_link when available, independently of raw payload. Compact clients omit links by default. false removes links including nested raw links."},{"name":"include_raw_payload","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Raw Payload"},"description":"Include bookmaker-specific payload. Compact clients default to false and receive runners[].win_odds; Sportsbet raw WIN prices are payload.horse_data[].odds."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"Include source metadata such as updated_at_ts (Unix seconds). Compact clients omit it by default."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"Include bookmaker snapshots without compact WIN or PLACE runner prices. This does not restrict odds to status fetching; early status ok may contain valid prices."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RacingOddsSnapshotResponse"},"examples":{"default":{"summary":"Racing odds: Snapshot","value":{"event_id":"race-1001","as_of_ts_ms":1760000000000,"active_runners":7,"total_runners":8,"scratched_runners":1,"runner_count_source":"betfair","runner_count_updated_at_ts":1759999700,"runner_count_complete":true,"items":[{"bookmaker_name":"betfair","race_id":"race-1001","status":"ok","total_matched":24567.12,"runners":[{"runner_number":"1","runner_name":"Example Runner","win_odds":3.4,"place_odds":1.65,"traded_volume":4100.0}]}],"resume":"1760000000000-0"}}}}}},"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 bookmaker odds snapshots for one racing event. Cache the last good snapshot with its timestamp and prefer streams for near-jump realtime displays. Compact WIN prices are at items[].runners[].win_odds. Exchange WIN-market volume is at items[].total_matched and matched runner volume, when supplied, is at items[].runners[].traded_volume. These are cumulative traded amounts, not current executable depth. Sportsbet raw prices, when requested, are at items[].payload.horse_data[].odds. Early odds status ok and near-jump status fetching can both contain valid prices. Top-level active_runners, total_runners, scratched_runners, runner_count_source, runner_count_updated_at_ts, and runner_count_complete describe the best available field state. The API key's betting-opportunity bookmaker selection does not filter racing odds. Request include_source=true for snapshot timestamps and include_links=true for bookmaker_link, independently of include_raw_payload.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/racing/events/{event_id}/odds/stream":{"get":{"tags":["Racing odds"],"summary":"Racing odds: Stream (SSE)","operationId":"stream_racing_odds_racing_events__event_id__odds_stream_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"include_links","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Links"},"description":"Include bookmaker_link when available, independently of raw payload. Compact clients omit links by default. false removes links including nested raw links."},{"name":"include_raw_payload","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Raw Payload"},"description":"Include bookmaker-specific payload. Compact clients default to false and receive runners[].win_odds; Sportsbet raw WIN prices are payload.horse_data[].odds."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"Include source metadata such as updated_at_ts (Unix seconds). Compact clients omit it by default."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"Include bookmaker snapshots without compact WIN or PLACE runner prices. This does not restrict odds to status fetching; early status ok may contain valid prices."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Redis stream ID.","title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."},{"name":"max_batch","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Max Batch"},"description":"Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients."}],"responses":{"200":{"description":"Server-Sent Events stream. Each message has an event name and JSON data payload.","content":{"text/event-stream":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/RacingOddsStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Decoded delta message","value":{"event":"delta","data":{"event_id":"race-1001","resume":"1760000000000-0","changes":[{"op":"upsert","bookmaker_name":"sportsbet","snapshot":{"bookmaker_name":"betfair","race_id":"race-1001","status":"ok","total_matched":24567.12,"runners":[{"runner_number":"1","runner_name":"Example Runner","win_odds":3.4,"place_odds":1.65,"traded_volume":4100.0}]}}]}}},"heartbeat":{"summary":"Decoded heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Decoded resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Server-Sent Events feed for racing odds changes on one event. Reconnect with `since=<last_resume>` and reload the snapshot after `resync`. Each changes[].snapshot uses the same compact runner fields, link flags and odds status meanings as the racing odds snapshot endpoint for compact clients. Non-compact clients with include_raw_payload=true receive raw bookmaker data directly in snapshot (Sportsbet: snapshot.horse_data[].odds), without a payload wrapper.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/racing/events/{event_id}/odds/ws":{"get":{"operationId":"websocket_racing_events_event_id_odds_ws","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"include_links","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Links"},"description":"Include bookmaker_link when available, independently of raw payload. Compact clients omit links by default. false removes links including nested raw links."},{"name":"include_raw_payload","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Raw Payload"},"description":"Include bookmaker-specific payload. Compact clients default to false and receive runners[].win_odds; Sportsbet raw WIN prices are payload.horse_data[].odds."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"Include source metadata such as updated_at_ts (Unix seconds). Compact clients omit it by default."},{"name":"include_unavailable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Unavailable"},"description":"Include bookmaker snapshots without compact WIN or PLACE runner prices. This does not restrict odds to status fetching; early status ok may contain valid prices."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Redis stream ID.","title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."},{"name":"max_batch","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Max Batch"},"description":"Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients."}],"responses":{"101":{"description":"WebSocket connection established. Messages are JSON objects.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/RacingOddsStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"event_id":"race-1001","resume":"1760000000000-0","changes":[{"op":"upsert","bookmaker_name":"sportsbet","snapshot":{"bookmaker_name":"betfair","race_id":"race-1001","status":"ok","total_matched":24567.12,"runners":[{"runner_number":"1","runner_name":"Example Runner","win_odds":3.4,"place_odds":1.65,"traded_volume":4100.0}]}}]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"OpenAPI tooling compatibility response schema. Runtime WebSocket connections upgrade with 101.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/RacingOddsStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"event_id":"race-1001","resume":"1760000000000-0","changes":[{"op":"upsert","bookmaker_name":"sportsbet","snapshot":{"bookmaker_name":"betfair","race_id":"race-1001","status":"ok","total_matched":24567.12,"runners":[{"runner_number":"1","runner_name":"Example Runner","win_odds":3.4,"place_odds":1.65,"traded_volume":4100.0}]}}]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}}},"x-websocket":true,"x-stream-equivalent":"/racing/events/{event_id}/odds/stream","tags":["Racing odds"],"summary":"Racing odds: Stream (WebSocket)","description":"WebSocket feed for racing odds changes on one event. Messages mirror the racing odds SSE feed.","security":[{"ApiKeyAuth":[]}],"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/bets/snapshot":{"get":{"tags":["Betting opportunities"],"summary":"Betting opportunities: Snapshot","operationId":"snapshot_bets_snapshot_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"strategies","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated strategies or 'all'","default":"all","title":"Strategies"},"description":"Comma-separated betting strategies or `all`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20000,"minimum":1,"default":2000,"title":"Limit"},"description":"Maximum items to return. Respect the caps returned by `/limits`."},{"name":"event_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Live bet source: active, legacy, or admin-only hybrid","title":"Source"},"description":"Live bet source: active, legacy, or admin-only hybrid"},{"name":"price_fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Fields"},"description":"`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields."},{"name":"include_links","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Links"},"description":"When true, include bookmaker/deep-link fields such as match links and racing links."},{"name":"include_raw_payload","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Raw Payload"},"description":"When true, include raw stored payload/data objects where the endpoint exposes them."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_debug_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Debug Ids"},"description":"When true, include internal/subgroup/opposing IDs useful for reconciliation."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BetsSnapshotResponse"},"examples":{"default":{"summary":"Betting opportunities: Snapshot","value":{"items":[{"id":"example-positive-ev","strategy":"pos_ev","event_id":"3704597661","bookmaker_name":"Bet365","selection_key":"moneyline:home","odds":2.1,"ev":7.7}],"resume":"{\"pos_ev\":\"1760000000000-0\"}"}}}}}},"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 current betting opportunities by strategy. Use `strategies`, `limit`, and `event_id` to keep the payload scoped to what your product needs. Poll at a bounded interval, cache the last good response, and show execution-risk language before any user-facing bet action.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}},"/bets/stream":{"get":{"tags":["Betting opportunities"],"summary":"Betting opportunities: Stream (SSE)","operationId":"stream_bets_stream_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"strategies","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"default":"all","title":"Strategies"},"description":"Comma-separated betting strategies or `all`."},{"name":"event_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Live bet source: active, legacy, or admin-only hybrid","title":"Source"},"description":"Live bet source: active, legacy, or admin-only hybrid"},{"name":"price_fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Fields"},"description":"`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields."},{"name":"include_links","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Links"},"description":"When true, include bookmaker/deep-link fields such as match links and racing links."},{"name":"include_raw_payload","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Raw Payload"},"description":"When true, include raw stored payload/data objects where the endpoint exposes them."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_debug_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Debug Ids"},"description":"When true, include internal/subgroup/opposing IDs useful for reconciliation."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resume token from /snapshot (JSON mapping strategy->id)","title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."}],"responses":{"200":{"description":"Server-Sent Events stream. Each message has an event name and JSON data payload.","content":{"text/event-stream":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/BetsStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Decoded delta message","value":{"event":"delta","data":{"resume":"1760000000000-0","events":[]}}},"heartbeat":{"summary":"Decoded heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Decoded resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Server-Sent Events feed for betting opportunity inserts, updates, and removals. Use this for alerting instead of high-frequency snapshot polling. Source-binding response headers identify the requested logical live-bet source and per-strategy effective sources.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/bets/ws":{"get":{"operationId":"websocket_bets_ws","parameters":[{"name":"strategies","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"default":"all","title":"Strategies"},"description":"Comma-separated betting strategies or `all`."},{"name":"event_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id"},"description":"Canonical event or race identifier from an event list response."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Live bet source: active, legacy, or admin-only hybrid","title":"Source"},"description":"Live bet source: active, legacy, or admin-only hybrid"},{"name":"price_fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Fields"},"description":"`odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields."},{"name":"include_links","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Links"},"description":"When true, include bookmaker/deep-link fields such as match links and racing links."},{"name":"include_raw_payload","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Raw Payload"},"description":"When true, include raw stored payload/data objects where the endpoint exposes them."},{"name":"include_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Source"},"description":"When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept."},{"name":"include_debug_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include Debug Ids"},"description":"When true, include internal/subgroup/opposing IDs useful for reconciliation."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resume token from /snapshot (JSON mapping strategy->id)","title":"Since"},"description":"Resume token from a previous snapshot or stream message. Pass it after reconnecting."},{"name":"catchup","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Catchup"},"description":"When true, return available missed stream events after `since` before waiting for new events."},{"name":"heartbeat_sec","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":5,"default":15,"title":"Heartbeat Sec"},"description":"Heartbeat interval in seconds for stream liveness. Valid range is 5-120."}],"responses":{"101":{"description":"WebSocket connection established. Messages are JSON objects.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/BetsStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"resume":"1760000000000-0","events":[]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"OpenAPI tooling compatibility response schema. Runtime WebSocket connections upgrade with 101.","content":{"application/json":{"schema":{"type":"object","description":"Decoded stream message. SSE transports this as an event line plus JSON data; WebSockets send it as JSON.","required":["event","data"],"properties":{"event":{"type":"string","enum":["delta","heartbeat","resync"]},"data":{"oneOf":[{"$ref":"#/components/schemas/BetsStreamEvent"},{"$ref":"#/components/schemas/StreamHeartbeat"},{"$ref":"#/components/schemas/StreamResyncEvent"}]}}},"examples":{"delta":{"summary":"Delta message","value":{"event":"delta","data":{"resume":"1760000000000-0","events":[]}}},"heartbeat":{"summary":"Heartbeat message","value":{"event":"heartbeat","data":{}}},"resync":{"summary":"Resync message","value":{"event":"resync","data":{"event_id":"3704597661","resume":null,"reason":"trimmed"}}}}}}}},"x-websocket":true,"x-stream-equivalent":"/bets/stream","tags":["Betting opportunities"],"summary":"Betting opportunities: Stream (WebSocket)","description":"WebSocket feed for betting opportunity inserts, updates, and removals. The accept handshake includes source-binding headers matching the SSE stream.","security":[{"ApiKeyAuth":[]}],"x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops.","x-stream-metering":{"stream_units":1,"stream_hours_formula":"stream_units * open_seconds / 3600","logical_bytes_metered":true,"quota_policy":"hard_cap","applies_to":"authenticated API-key connections"}}},"/events/{event_id}/results":{"get":{"tags":["Results"],"summary":"Results: Event result","operationId":"get_event_results_events__event_id__results_get","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Canonical event or race identifier from an event list response."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResultItem"},"examples":{"default":{"summary":"Results: Event result","value":{"event_id":"3704597661","status":"final","result":{"home_score":24,"away_score":18}}}}}}},"400":{"description":"Invalid request parameters or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Credentials are valid but do not allow this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying when supplied by the limiter.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Request bucket capacity for the active limiter bucket when supplied.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Approximate remaining requests in the active limiter bucket when supplied.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Bucket":{"description":"Limiter bucket name that produced the response when supplied.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"description":"Returns the latest known result for a sports event, or `pending` until settled. Poll every 1-5 minutes after start, then back off once the event is final.","x-rate-limit-note":"Handle HTTP 429 with backoff and avoid tight polling loops."}}},"components":{"schemas":{"BookmakersResponse":{"properties":{"event_id":{"type":"string","title":"Event Id"},"items":{"items":{"type":"string"},"type":"array","title":"Items"}},"type":"object","required":["event_id","items"],"title":"BookmakersResponse"},"CoverageBookmaker":{"properties":{"bookmaker":{"type":"string","title":"Bookmaker"},"country_codes":{"items":{"type":"string"},"type":"array","title":"Country Codes"}},"type":"object","required":["bookmaker"],"title":"CoverageBookmaker"},"CoverageLeague":{"properties":{"sport":{"type":"string","title":"Sport"},"league":{"type":"string","title":"League"}},"type":"object","required":["sport","league"],"title":"CoverageLeague"},"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"},"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"},"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"},"EventDetail":{"properties":{"event_id":{"type":"string","title":"Event Id"},"data":{"additionalProperties":true,"type":"object","title":"Data"}},"type":"object","required":["event_id","data"],"title":"EventDetail"},"EventListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/EventSummary"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["items","count"],"title":"EventListResponse"},"EventSummary":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"Canonical event identifier (game_masterID)."},"sport":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sport"},"league":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"League","description":"League name representation."},"start_time":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Start Time","description":"Unix seconds."},"home_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Team"},"away_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Away Team"},"event_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event State","description":"Effective lifecycle state, including inferred start_time_passed."},"event_state_certainty":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event State Certainty"},"event_state_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event State Source"},"state_observed_at":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"State Observed At","description":"Unix seconds."},"actual_started_at":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Actual Started At","description":"Unix seconds."},"last_live_at":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Live At","description":"Unix seconds."},"finished_at":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Finished At","description":"Unix seconds."},"live_candidate":{"type":"boolean","title":"Live Candidate","default":false},"event_state_stale":{"type":"boolean","title":"Event State Stale","default":false},"has_available_bets":{"type":"boolean","title":"Has Available Bets","description":"Whether the event currently has at least one live normalized odds line.","default":false},"last_capture":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Capture","description":"Unix seconds."},"bookmakers":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":"object","title":"Bookmakers","description":"Bookmaker -> odds_data_id."},"league_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"League Image Url","description":"Azure-hosted league image URL when configured for this league."},"league_image_asset_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"League Image Asset Slug","description":"Storage slug for the configured Azure-hosted league image."},"home_team_logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Team Logo Url","description":"Azure-hosted home-team logo URL when matched for this event."},"away_team_logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Away Team Logo Url","description":"Azure-hosted away-team logo URL when matched for this event."},"team_logo_manifest_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team Logo Manifest Url","description":"Azure-hosted per-league team-logo manifest URL when configured."},"opportunity_counts":{"anyOf":[{"$ref":"#/components/schemas/OpportunityCounts"},{"type":"null"}],"description":"Aggregate live opportunity counts when requested."}},"type":"object","required":["event_id"],"title":"EventSummary"},"ExchangeMarket":{"properties":{"market_id":{"type":"string","title":"Market Id","description":"Stable opaque ID to pass to the multiplexed orderbook WebSocket."},"event_id":{"type":"string","title":"Event Id","description":"Streaming event identity; may be betfair:<id> for events outside the schedule."},"wagerwise_event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wagerwise Event Id"},"betfair_event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Betfair Event Id"},"exchange":{"type":"string","title":"Exchange"},"sport":{"type":"string","title":"Sport"},"market_key":{"type":"string","title":"Market Key"},"bet_type":{"type":"string","title":"Bet Type"},"metric":{"type":"string","title":"Metric"},"period":{"type":"string","title":"Period"},"source_market_type":{"type":"string","title":"Source Market Type"},"betting_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Betting Type"},"market_name":{"type":"string","title":"Market Name"},"event_name":{"type":"string","title":"Event Name"},"competition_name":{"type":"string","title":"Competition Name"},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time"},"in_play_supported":{"type":"boolean","title":"In Play Supported"},"ladder_depth_max":{"type":"integer","title":"Ladder Depth Max"},"number_of_winners":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number Of Winners"},"total_matched":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Matched"},"runners":{"items":{"$ref":"#/components/schemas/ExchangeMarketRunner"},"type":"array","title":"Runners"}},"type":"object","required":["market_id","event_id","exchange","sport","market_key","bet_type","metric","period","source_market_type","market_name","event_name","competition_name","in_play_supported","ladder_depth_max"],"title":"ExchangeMarket"},"ExchangeMarketRunner":{"properties":{"selection_id":{"type":"string","title":"Selection Id"},"name":{"type":"string","title":"Name"},"handicap":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Handicap"},"sort_priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sort Priority"}},"type":"object","required":["selection_id","name"],"title":"ExchangeMarketRunner"},"ExchangeMarketSubscription":{"properties":{"websocket_url":{"type":"string","title":"Websocket Url"},"command":{"additionalProperties":true,"type":"object","title":"Command"},"max_markets_per_connection":{"type":"integer","title":"Max Markets Per Connection"}},"type":"object","required":["websocket_url","command","max_markets_per_connection"],"title":"ExchangeMarketSubscription"},"ExchangeMarketsResponse":{"properties":{"score_subscription":{"anyOf":[{"$ref":"#/components/schemas/TennisScoreSubscription"},{"type":"null"}]},"event_id":{"type":"string","title":"Event Id"},"exchange":{"type":"string","title":"Exchange"},"count":{"type":"integer","title":"Count"},"available_market_types":{"items":{"type":"string"},"type":"array","title":"Available Market Types"},"markets":{"items":{"$ref":"#/components/schemas/ExchangeMarket"},"type":"array","title":"Markets"},"subscription":{"$ref":"#/components/schemas/ExchangeMarketSubscription"}},"type":"object","required":["event_id","exchange","count","available_market_types","markets","subscription"],"title":"ExchangeMarketsResponse"},"ExchangeOrderBookItem":{"properties":{"id":{"type":"string","title":"Id"},"market_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Id"},"event_id":{"type":"string","title":"Event Id"},"exchange":{"type":"string","title":"Exchange"},"exchange_market_id":{"type":"string","title":"Exchange Market Id"},"source_market_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Market Type"},"sport":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sport"},"betting_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Betting Type"},"market_key":{"type":"string","title":"Market Key"},"market_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Name"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"bet_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bet Type"},"period":{"anyOf":[{"type":"integer"},{"type":"string"}],"title":"Period"},"period_str":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Str"},"metric":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metric"},"line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Line"},"home_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Team"},"away_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Away Team"},"home_team_names":{"items":{"type":"string"},"type":"array","title":"Home Team Names"},"away_team_names":{"items":{"type":"string"},"type":"array","title":"Away Team Names"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"in_play":{"type":"boolean","title":"In Play","default":false},"total_matched":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Matched","description":"Exchange-native matched or displayed market volume."},"total_available":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Available","description":"Native or displayed-ladder total currently available market liquidity."},"total_available_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Total Available Source","description":"Whether total_available is exchange-native or summed from displayed ladders."},"traded_volume":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Traded Volume","description":"Exchange-native traded market volume."},"double_stake_volume":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Double Stake Volume","description":"Twice the back stake for each execution, when supplied by Smarkets."},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency","description":"ISO 4217 currency for monetary volume fields, such as GBP for Smarkets."},"source_ts":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Ts"},"source_age_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Source Age Seconds"},"freshness":{"type":"string","title":"Freshness","description":"Price observation state: live, warming, or stale.","default":"stale"},"actively_collected":{"type":"boolean","title":"Actively Collected","default":false},"source_delayed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Source Delayed"},"source_conflate_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Source Conflate Ms"},"observed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Observed At","description":"UTC timestamp when WagerWise observed this order book."},"selections":{"items":{"$ref":"#/components/schemas/ExchangeSelectionBook"},"type":"array","title":"Selections"}},"type":"object","required":["id","event_id","exchange","exchange_market_id","market_key","period"],"title":"ExchangeOrderBookItem"},"ExchangeOrderBookSnapshotResponse":{"properties":{"event_id":{"type":"string","title":"Event Id"},"as_of_ts_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Ts Ms"},"ttl_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttl Seconds"},"items":{"items":{"$ref":"#/components/schemas/ExchangeOrderBookItem"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"resume":{"type":"string","title":"Resume","description":"Redis stream ID to resume from; pass as 'since' to /stream."},"refresh_state":{"type":"string","title":"Refresh State","default":"not_requested"},"fresh_count":{"type":"integer","title":"Fresh Count","default":0},"stale_count":{"type":"integer","title":"Stale Count","default":0},"warming_count":{"type":"integer","title":"Warming Count","default":0}},"type":"object","required":["event_id","resume"],"title":"ExchangeOrderBookSnapshotResponse"},"ExchangePriceLevel":{"properties":{"price":{"type":"number","title":"Price"},"size":{"type":"number","title":"Size"}},"type":"object","required":["price","size"],"title":"ExchangePriceLevel"},"ExchangeSelectionBook":{"properties":{"selection_key":{"type":"string","title":"Selection Key"},"exchange_selection_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exchange Selection Id"},"selection_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Selection Name"},"handicap":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Handicap"},"side":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Side"},"line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Line"},"player_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Player Name"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"last_traded_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Last Traded Price"},"traded_volume":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Traded Volume"},"traded_volume_by_price":{"items":{"$ref":"#/components/schemas/ExchangePriceLevel"},"type":"array","title":"Traded Volume By Price"},"available_to_back":{"items":{"$ref":"#/components/schemas/ExchangePriceLevel"},"type":"array","title":"Available To Back"},"available_to_lay":{"items":{"$ref":"#/components/schemas/ExchangePriceLevel"},"type":"array","title":"Available To Lay"},"best_back_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Back Price"},"best_back_size":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Back Size"},"best_lay_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Lay Price"},"best_lay_size":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Lay Size"}},"type":"object","required":["selection_key"],"title":"ExchangeSelectionBook"},"OddLine":{"properties":{"id":{"type":"string","title":"Id"},"event_id":{"type":"string","title":"Event Id"},"bookmaker":{"type":"string","title":"Bookmaker"},"bookmaker_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bookmaker Name"},"source_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Type"},"exchange":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exchange"},"exchange_market_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exchange Market Id"},"exchange_selection_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exchange Selection Id"},"selection_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Selection Key"},"market_group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Group Id"},"market_key":{"type":"string","title":"Market Key"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"bet_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bet Type"},"period":{"anyOf":[{"type":"integer"},{"type":"string"}],"title":"Period"},"period_str":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Str"},"metric":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metric"},"line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Line"},"side":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Side"},"player_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Player Name"},"selection_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Selection Name"},"market_contract":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Market Contract","description":"Categorical market contract: version, source_rules, scope, and source_market. Part of market identity; different source contracts are not assumed settlement-equivalent."},"selection_parameters":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Selection Parameters","description":"Structured categorical outcome: predicate, methods, rounds, includes_decision and includes_draw as applicable. Finishing rounds are outcomes within full time, not periods."},"number_of_outcomes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number Of Outcomes"},"odds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Odds"},"odds_no_vig":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Odds No Vig"},"odds_no_vig_multiplicative_method":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Odds No Vig Multiplicative Method"},"odds_no_vig_additive_method":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Odds No Vig Additive Method"},"odds_no_vig_power_method":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Odds No Vig Power Method"},"odds_no_vig_shin_method":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Odds No Vig Shin Method"},"fair_odds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Odds"},"main_line_eligible":{"type":"boolean","title":"Main Line Eligible","default":true},"best_back_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Back Price"},"best_back_size":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Back Size"},"best_lay_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Lay Price"},"best_lay_size":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Lay Size"},"last_traded_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Last Traded Price"},"traded_volume":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Traded Volume"},"total_matched":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Matched"},"liquidity_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Liquidity Source"},"liquidity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Liquidity"},"back_liquidity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Back Liquidity"},"lay_liquidity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lay Liquidity"},"is_available":{"type":"boolean","title":"Is Available","default":true}},"type":"object","required":["id","event_id","bookmaker","market_key","period"],"title":"OddLine"},"OddsHistoryMeta":{"properties":{"from_ts":{"type":"string","format":"date-time","title":"From Ts"},"to_ts":{"type":"string","format":"date-time","title":"To Ts"},"available_price_types":{"items":{"type":"string"},"type":"array","title":"Available Price Types"},"source":{"type":"string","title":"Source","default":"unknown"},"degraded":{"type":"boolean","title":"Degraded","default":false},"degraded_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Degraded Reason"}},"type":"object","required":["from_ts","to_ts","available_price_types"],"title":"OddsHistoryMeta"},"OddsHistoryPoint":{"properties":{"tick_ts":{"type":"string","format":"date-time","title":"Tick Ts"},"is_available":{"type":"boolean","title":"Is Available"},"value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Value"},"odds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Odds"},"odds_no_vig":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Odds No Vig"},"fair_odds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Odds"}},"type":"object","required":["tick_ts","is_available"],"title":"OddsHistoryPoint"},"OddsHistoryResponse":{"properties":{"event_id":{"type":"string","title":"Event Id"},"selection_key":{"type":"string","title":"Selection Key"},"price_type":{"type":"string","title":"Price Type"},"series":{"items":{"$ref":"#/components/schemas/OddsHistorySeries"},"type":"array","title":"Series"},"meta":{"$ref":"#/components/schemas/OddsHistoryMeta"}},"type":"object","required":["event_id","selection_key","price_type","series","meta"],"title":"OddsHistoryResponse"},"OddsHistorySeries":{"properties":{"bookmaker_name":{"type":"string","title":"Bookmaker Name"},"points":{"items":{"$ref":"#/components/schemas/OddsHistoryPoint"},"type":"array","title":"Points"}},"type":"object","required":["bookmaker_name","points"],"title":"OddsHistorySeries"},"OddsSnapshotResponse":{"properties":{"event_id":{"type":"string","title":"Event Id"},"as_of_ts_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Ts Ms","description":"Time the API accepted the latest successful snapshot or authoritative bookmaker subset."},"snapshot_capture_ts_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Snapshot Capture Ts Ms","description":"Time the snapshot producer completed the latest published event capture."},"bookmaker_as_of_ts_ms":{"additionalProperties":{"type":"integer"},"type":"object","title":"Bookmaker As Of Ts Ms","description":"Last API acceptance time for each bookmaker included in this response."},"oldest_bookmaker_as_of_ts_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Oldest Bookmaker As Of Ts Ms","description":"Oldest acceptance time among bookmaker_as_of_ts_ms values."},"target_refresh_interval_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Target Refresh Interval Seconds","description":"Current scheduler target for this event; source health and runtime load can add delay."},"ttl_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttl Seconds"},"items":{"items":{"$ref":"#/components/schemas/OddLine"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"complete":{"type":"boolean","title":"Complete","description":"True when there are no more bookmaker-complete pages for this request.","default":true},"bookmakers_included":{"items":{"type":"string"},"type":"array","title":"Bookmakers Included","description":"Bookmakers whose complete matching line sets are included in this response."},"bookmaker_counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Bookmaker Counts","description":"Number of returned matching lines for each included bookmaker."},"resume":{"type":"string","title":"Resume","description":"Opaque stream resume token; pass it as 'since' to /stream."}},"type":"object","required":["event_id","items","resume"],"title":"OddsSnapshotResponse"},"OpportunityCounts":{"properties":{"positive_ev":{"type":"integer","title":"Positive Ev","default":0},"arbitrage":{"type":"integer","title":"Arbitrage","default":0},"middle":{"type":"integer","title":"Middle","default":0},"bonus_conversion":{"type":"integer","title":"Bonus Conversion","default":0}},"type":"object","title":"OpportunityCounts"},"PredictionContractBook":{"properties":{"contract_id":{"type":"string","title":"Contract Id"},"source_contract_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Contract Id"},"contract_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contract Name"},"outcome":{"type":"string","title":"Outcome"},"side":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Side"},"line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Line"},"player_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Player Name"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"probability_bids":{"items":{"$ref":"#/components/schemas/PredictionPriceLevel"},"type":"array","title":"Probability Bids"},"probability_asks":{"items":{"$ref":"#/components/schemas/PredictionPriceLevel"},"type":"array","title":"Probability Asks"},"best_bid_probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Bid Probability"},"best_bid_size":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Bid Size"},"best_ask_probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Ask Probability"},"best_ask_size":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Ask Size"},"last_trade_probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Last Trade Probability"},"gross_decimal_odds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gross Decimal Odds"},"fee_adjusted_decimal_odds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fee Adjusted Decimal Odds"},"estimated_fee_per_contract":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Estimated Fee Per Contract"},"projection_eligible":{"type":"boolean","title":"Projection Eligible","default":false}},"type":"object","required":["contract_id","outcome"],"title":"PredictionContractBook"},"PredictionOrderBookItem":{"properties":{"id":{"type":"string","title":"Id"},"event_id":{"type":"string","title":"Event Id"},"provider":{"type":"string","title":"Provider"},"provider_event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Event Id"},"provider_market_id":{"type":"string","title":"Provider Market Id"},"market_key":{"type":"string","title":"Market Key"},"market_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Name"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"bet_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bet Type"},"period":{"anyOf":[{"type":"integer"},{"type":"string"}],"title":"Period"},"period_str":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Str"},"metric":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metric"},"player_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Player Name"},"home_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Team"},"away_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Away Team"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"in_play":{"type":"boolean","title":"In Play","default":false},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"size_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Size Unit"},"total_volume":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Volume"},"total_liquidity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Liquidity"},"fee_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fee Model"},"fee_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fee Rate"},"fee_multiplier":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fee Multiplier"},"fee_exponent":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fee Exponent"},"fee_estimated":{"type":"boolean","title":"Fee Estimated","default":true},"source_ts":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Ts"},"observed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Observed At"},"contracts":{"items":{"$ref":"#/components/schemas/PredictionContractBook"},"type":"array","title":"Contracts"}},"type":"object","required":["id","event_id","provider","provider_market_id","market_key","period"],"title":"PredictionOrderBookItem"},"PredictionOrderBookSnapshotResponse":{"properties":{"event_id":{"type":"string","title":"Event Id"},"as_of_ts_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Ts Ms"},"ttl_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttl Seconds"},"items":{"items":{"$ref":"#/components/schemas/PredictionOrderBookItem"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"resume":{"type":"string","title":"Resume","description":"Redis stream ID to resume from; pass as 'since' to /stream."}},"type":"object","required":["event_id","resume"],"title":"PredictionOrderBookSnapshotResponse"},"PredictionPriceLevel":{"properties":{"price":{"type":"number","exclusiveMaximum":1.0,"exclusiveMinimum":0.0,"title":"Price"},"size":{"type":"number","minimum":0.0,"title":"Size"}},"type":"object","required":["price","size"],"title":"PredictionPriceLevel"},"PublicStatusComponent":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"type":"string","title":"Status"},"latency_source":{"type":"string","title":"Latency Source","default":"http"},"metrics":{"$ref":"#/components/schemas/PublicStatusMetrics"}},"type":"object","required":["id","name","status"],"title":"PublicStatusComponent"},"PublicStatusMetrics":{"properties":{"uptime_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Pct"},"p50_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P50 Ms"},"p95_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P95 Ms"},"p99_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P99 Ms"},"error_rate_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Error Rate Pct"}},"type":"object","title":"PublicStatusMetrics"},"PublicStatusRateLimits":{"properties":{"status":{"type":"string","title":"Status"},"throttled_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Throttled Pct"}},"type":"object","required":["status"],"title":"PublicStatusRateLimits"},"PublicStatusResponse":{"properties":{"status":{"type":"string","title":"Status"},"as_of":{"type":"string","title":"As Of"},"window_seconds":{"type":"integer","title":"Window Seconds"},"components":{"items":{"$ref":"#/components/schemas/PublicStatusComponent"},"type":"array","title":"Components"},"rate_limits":{"$ref":"#/components/schemas/PublicStatusRateLimits"},"source":{"$ref":"#/components/schemas/PublicStatusSource"},"recent_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recent Status"},"recent_window_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Recent Window Seconds"},"recent_components":{"items":{"$ref":"#/components/schemas/PublicStatusComponent"},"type":"array","title":"Recent Components"},"recent_rate_limits":{"anyOf":[{"$ref":"#/components/schemas/PublicStatusRateLimits"},{"type":"null"}]}},"type":"object","required":["status","as_of","window_seconds","rate_limits","source"],"title":"PublicStatusResponse"},"PublicStatusSource":{"properties":{"fresh":{"type":"boolean","title":"Fresh"},"age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Age Seconds"}},"type":"object","required":["fresh"],"title":"PublicStatusSource"},"RacingEventDetail":{"properties":{"event_id":{"type":"string","title":"Event Id"},"data":{"additionalProperties":true,"type":"object","title":"Data"}},"type":"object","required":["event_id","data"],"title":"RacingEventDetail"},"RacingEventListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/RacingEventSummary"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["count"],"title":"RacingEventListResponse"},"RacingEventSummary":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"Canonical racing event identifier (raceID)."},"race_start_time":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Race Start Time","description":"Unix seconds."},"race_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Type"},"race_venue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Venue"},"race_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Number"},"race_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race State"},"race_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Country"},"race_distance":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Distance"},"active_runners":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Active Runners","description":"Best available active-runner count after known scratchings."},"total_runners":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Runners","description":"Declared field size before known scratchings, when available."},"scratched_runners":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Scratched Runners","description":"Known scratched/removed runner count."},"runner_count_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Count Source","description":"Bookmaker or schedule source used for the runner count."},"runner_count_updated_at_ts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Runner Count Updated At Ts","description":"Runner-count source time in Unix seconds."},"runner_count_complete":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Runner Count Complete","description":"True only when the source declares state for the complete field."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Event lifecycle status. An open event can already have early bookmaker odds; fetching denotes active near-jump collection. Distinct from bookmaker snapshot statuses such as ok. Omit the status filter when discovering early races."},"bookmaker_links":{"additionalProperties":true,"type":"object","title":"Bookmaker Links"},"scrapping_links":{"additionalProperties":true,"type":"object","title":"Scrapping Links"},"last_capture":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Capture","description":"Unix seconds."}},"type":"object","required":["event_id"],"title":"RacingEventSummary"},"RacingOddsBookmakerSnapshot":{"properties":{"bookmaker_name":{"type":"string","title":"Bookmaker Name"},"race_id":{"type":"string","title":"Race Id"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Bookmaker snapshot status, distinct from event status. Early prices use ok; near-jump collection uses fetching. Both may contain valid prices; do not require fetching to read odds. Other statuses may occur; inspect runner prices and timestamps."},"total_matched":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Matched","description":"Cumulative amount matched on the exchange WIN market in the source feed's currency. Betfair racing is currently requested in AUD."},"updated_at_ts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Updated At Ts","description":"Snapshot update time in Unix seconds. On compact clients, request include_source=true to include it."},"payload":{"additionalProperties":true,"type":"object","title":"Payload","description":"Bookmaker-specific payload, omitted when include_raw_payload=false (the compact default). Sportsbet WIN prices use payload.horse_data[].odds, with horse_name and number; payload.runners[].win_odds is not a universal raw schema.","examples":[{"horse_data":[{"horse_name":"Example Runner","number":"1","odds":3.4}],"status":"ok"}]},"runners":{"items":{"properties":{"runner_number":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Runner Number"},"runner_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Name"},"win_odds":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Win Odds","description":"Decimal WIN price, when supplied."},"place_odds":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Place Odds","description":"Decimal PLACE price, when supplied."},"jockey_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jockey Name"},"trainer_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trainer Name"},"selection_id":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Selection Id"},"runner_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Status"},"is_scratched":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Scratched"},"removal_date":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Removal Date"},"adjustment_factor":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Adjustment Factor"},"traded_volume":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Traded Volume","description":"Cumulative exchange-matched amount for this runner in the source feed's currency. Betfair racing is currently requested in AUD."}},"type":"object","title":"RacingOddsRunner","description":"Compact runner fields; absent source values are omitted from responses."},"type":"array","description":"Compact prices at items[].runners[].win_odds. Present for priced runners on compact clients, or when include_raw_payload=false. Runners without WIN or PLACE prices are omitted."},"bookmaker_link":{"type":"string","description":"Source race URL, when available and include_links=true. Independent of include_raw_payload; also applies to stream snapshots."}},"type":"object","required":["bookmaker_name","race_id"],"title":"RacingOddsBookmakerSnapshot"},"RacingOddsSnapshotResponse":{"properties":{"event_id":{"type":"string","title":"Event Id"},"as_of_ts_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Ts Ms"},"ttl_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttl Seconds"},"active_runners":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Active Runners"},"total_runners":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Runners"},"scratched_runners":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Scratched Runners"},"runner_count_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Runner Count Source"},"runner_count_updated_at_ts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Runner Count Updated At Ts"},"runner_count_complete":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Runner Count Complete"},"items":{"items":{"$ref":"#/components/schemas/RacingOddsBookmakerSnapshot"},"type":"array","title":"Items"},"resume":{"type":"string","title":"Resume","description":"Redis stream ID to resume from; pass as 'since' to /stream."}},"type":"object","required":["event_id","resume"],"title":"RacingOddsSnapshotResponse"},"ResultItem":{"properties":{"event_id":{"type":"string","title":"Event Id"},"status":{"type":"string","title":"Status","description":"available|pending"},"result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result"}},"type":"object","required":["event_id","status"],"title":"ResultItem"},"TennisScoreSubscription":{"properties":{"websocket_url":{"type":"string","title":"Websocket Url"},"command":{"additionalProperties":true,"type":"object","title":"Command"},"max_events_per_connection":{"type":"integer","title":"Max Events Per Connection"}},"type":"object","required":["websocket_url","command","max_events_per_connection"],"title":"TennisScoreSubscription"},"WidgetBookmakerOdds":{"properties":{"label":{"type":"string","title":"Label"},"selections":{"items":{"$ref":"#/components/schemas/WidgetSelectionPrice"},"type":"array","title":"Selections"}},"type":"object","required":["label"],"title":"WidgetBookmakerOdds"},"WidgetEvent":{"properties":{"league":{"type":"string","title":"League"},"event_name":{"type":"string","title":"Event Name"},"start_time":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Start Time"},"last_updated":{"type":"string","title":"Last Updated"},"markets":{"items":{"$ref":"#/components/schemas/WidgetMarket"},"type":"array","title":"Markets"}},"type":"object","required":["league","event_name","last_updated"],"title":"WidgetEvent"},"WidgetMarket":{"properties":{"market":{"type":"string","title":"Market"},"bookmakers":{"items":{"$ref":"#/components/schemas/WidgetBookmakerOdds"},"type":"array","title":"Bookmakers"}},"type":"object","required":["market"],"title":"WidgetMarket"},"WidgetOddsTickerResponse":{"properties":{"league":{"type":"string","title":"League"},"widget_id":{"type":"string","title":"Widget Id"},"last_updated":{"type":"string","title":"Last Updated"},"events":{"items":{"$ref":"#/components/schemas/WidgetEvent"},"type":"array","title":"Events"}},"type":"object","required":["league","widget_id","last_updated"],"title":"WidgetOddsTickerResponse"},"WidgetSelectionPrice":{"properties":{"selection":{"type":"string","title":"Selection"},"price":{"type":"number","title":"Price"}},"type":"object","required":["selection","price"],"title":"WidgetSelectionPrice"},"TennisScore":{"properties":{"sport":{"const":"tennis","title":"Sport","type":"string"},"provider":{"const":"betfair","title":"Provider","type":"string"},"home":{"$ref":"#/components/schemas/TennisScoreParticipant"},"away":{"$ref":"#/components/schemas/TennisScoreParticipant"},"current_set":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Current Set"},"current_game":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Current Game"},"match_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Match Status"},"tie_break":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Tie Break"},"received_at":{"description":"Odds API receipt timestamp, not source time.","title":"Received At","type":"string"},"source_timestamp":{"default":null,"title":"Source Timestamp","type":"null"},"source_timestamp_available":{"const":false,"default":false,"title":"Source Timestamp Available","type":"boolean"}},"required":["sport","provider","home","away","current_set","current_game","match_status","tie_break","received_at"],"title":"TennisScore","type":"object"},"TennisScoreFreshness":{"properties":{"state":{"enum":["live","stale"],"title":"State","type":"string"},"age_ms":{"title":"Age Ms","type":"integer"},"poll_interval_ms":{"title":"Poll Interval Ms","type":"integer"}},"required":["state","age_ms","poll_interval_ms"],"title":"TennisScoreFreshness","type":"object"},"TennisScoreParticipant":{"properties":{"name":{"title":"Name","type":"string"},"sets":{"minimum":0,"title":"Sets","type":"integer"},"games":{"minimum":0,"title":"Games","type":"integer"},"points":{"title":"Points","type":"string"},"is_serving":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Serving"},"game_sequence":{"items":{"type":"integer"},"title":"Game Sequence","type":"array"}},"required":["name","sets","games","points","is_serving","game_sequence"],"title":"TennisScoreParticipant","type":"object"},"TennisScoreMessage":{"properties":{"type":{"enum":["image","delta"],"title":"Type","type":"string"},"event_id":{"title":"Event Id","type":"string"},"stream_id":{"description":"Process-session ID that scopes sequence values.","pattern":"^[0-9a-f]{32}$","title":"Stream Id","type":"string"},"sequence":{"description":"Monotonic semantic revision within one stream_id.","minimum":1,"title":"Sequence","type":"integer"},"update_kind":{"description":"How this complete replacement relates to the last state delivered on this connection.","enum":["initial","advance","correction","reaffirmation","reset"],"title":"Update Kind","type":"string"},"score":{"$ref":"#/components/schemas/TennisScore"},"freshness":{"$ref":"#/components/schemas/TennisScoreFreshness"}},"required":["type","event_id","stream_id","sequence","update_kind","score","freshness"],"title":"TennisScoreMessage","type":"object"},"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."}}},"RateLimitResponse":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"}],"description":"Rate-limit response. Retry after the window or reduce polling frequency."},"ExchangeOrderBookStreamDelta":{"properties":{"op":{"title":"Op","type":"string"},"orderbook":{"$ref":"#/components/schemas/ExchangeOrderBookItem"}},"required":["op","orderbook"],"title":"ExchangeOrderBookStreamDelta","type":"object"},"ExchangeOrderBookStreamEvent":{"properties":{"event_id":{"title":"Event Id","type":"string"},"resume":{"title":"Resume","type":"string"},"changes":{"items":{"$ref":"#/components/schemas/ExchangeOrderBookStreamDelta"},"title":"Changes","type":"array"}},"required":["event_id","resume","changes"],"title":"ExchangeOrderBookStreamEvent","type":"object"},"PredictionOrderBookStreamDelta":{"properties":{"op":{"title":"Op","type":"string"},"orderbook":{"$ref":"#/components/schemas/PredictionOrderBookItem"}},"required":["op","orderbook"],"title":"PredictionOrderBookStreamDelta","type":"object"},"PredictionOrderBookStreamEvent":{"properties":{"event_id":{"title":"Event Id","type":"string"},"resume":{"title":"Resume","type":"string"},"changes":{"items":{"$ref":"#/components/schemas/PredictionOrderBookStreamDelta"},"title":"Changes","type":"array"}},"required":["event_id","resume","changes"],"title":"PredictionOrderBookStreamEvent","type":"object"},"OddsStreamDelta":{"properties":{"op":{"title":"Op","type":"string"},"odd":{"$ref":"#/components/schemas/OddLine"}},"required":["op","odd"],"title":"OddsStreamDelta","type":"object"},"OddsStreamEvent":{"properties":{"event_id":{"title":"Event Id","type":"string"},"resume":{"title":"Resume","type":"string"},"changes":{"items":{"$ref":"#/components/schemas/OddsStreamDelta"},"title":"Changes","type":"array"},"snapshot_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Snapshot Id"},"batch_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Batch Index"},"batch_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Batch Count"}},"required":["event_id","resume","changes"],"title":"OddsStreamEvent","type":"object"},"RacingOddsStreamDelta":{"properties":{"op":{"title":"Op","type":"string"},"bookmaker_name":{"title":"Bookmaker Name","type":"string"},"snapshot":{"anyOf":[{"$ref":"#/components/schemas/RacingOddsBookmakerSnapshot"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"description":"Compact clients receive the documented bookmaker snapshot with runners and optional bookmaker_link. Non-compact clients with include_raw_payload=true receive a bookmaker-specific raw object directly here (Sportsbet: snapshot.horse_data[].odds and snapshot.raceID), without a payload wrapper. Absent for removals.","title":"Snapshot"}},"required":["op","bookmaker_name"],"title":"RacingOddsStreamDelta","type":"object"},"RacingOddsStreamEvent":{"properties":{"event_id":{"title":"Event Id","type":"string"},"resume":{"title":"Resume","type":"string"},"changes":{"items":{"$ref":"#/components/schemas/RacingOddsStreamDelta"},"title":"Changes","type":"array"}},"required":["event_id","resume","changes"],"title":"RacingOddsStreamEvent","type":"object"},"ApiRootResponse":{"type":"object","required":["name","version","openapi","reference"],"properties":{"name":{"type":"string","example":"Odds API"},"version":{"type":"string","example":"1.0.0"},"openapi":{"type":"string","example":"/v1/openapi.json"},"reference":{"type":"string","example":"/v1/reference"}}},"ApiIdentityResponse":{"type":"object","description":"Authenticated API identity and enabled product capabilities.","additionalProperties":true,"properties":{"method":{"type":"string","example":"api_key"},"client_id":{"type":"string","nullable":true},"capabilities":{"type":"object","additionalProperties":true},"membership_tier":{"type":"integer","nullable":true}}},"UsageResponse":{"type":"object","description":"Quota and usage counters for the API key. New odds-api.net plans report API credits; legacy keys may still expose request-count fields.","additionalProperties":true,"properties":{"period_start_utc":{"type":"string","nullable":true},"period_end_utc":{"type":"string","nullable":true},"plan":{"type":"string","nullable":true},"pricing_model":{"type":"string","nullable":true},"api_credits_used":{"type":"integer","nullable":true},"api_credits_limit":{"type":"integer","nullable":true},"used":{"type":"integer","nullable":true},"limit":{"type":"integer","nullable":true},"request_count":{"type":"integer","nullable":true},"request_limit":{"type":"integer","nullable":true},"stream_hours_used":{"type":"number","format":"double","nullable":true},"stream_hours_limit":{"type":"integer","nullable":true},"stream_logical_bytes_used":{"type":"integer","nullable":true},"stream_logical_bytes_limit":{"type":"integer","nullable":true},"stream_concurrent_units_used":{"type":"integer","nullable":true},"stream_concurrent_units_limit":{"type":"integer","nullable":true}}},"LimitsResponse":{"type":"object","required":["responses","sse"],"properties":{"responses":{"type":"object","required":["events_limit_max","odds_snapshot_limit_max","bets_snapshot_limit_max"],"properties":{"events_limit_max":{"type":"integer","example":1000},"odds_snapshot_limit_max":{"type":"integer","example":25000},"bets_snapshot_limit_max":{"type":"integer","example":20000}}},"sse":{"type":"object","required":["heartbeat_sec_min","heartbeat_sec_max","max_batch_default"],"properties":{"heartbeat_sec_min":{"type":"integer","example":5},"heartbeat_sec_max":{"type":"integer","example":120},"max_batch_default":{"type":"integer","example":500}}},"streams":{"type":"object","additionalProperties":true,"description":"Shared stream-hour, logical-byte, and concurrent-unit rules for API-key SSE and WebSocket connections."},"plan_id":{"type":"string","nullable":true},"pricing_model":{"type":"string","nullable":true},"billing_status":{"type":"string","nullable":true},"quotas":{"type":"object","additionalProperties":true},"entitlements":{"type":"object","additionalProperties":true},"add_ons":{"type":"object","additionalProperties":true},"usage_unit":{"type":"string","nullable":true,"example":"api_credits"},"overage_policy":{"type":"string","nullable":true,"example":"hard_cap"}}},"StringListResponse":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"string"}}}},"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"}}},"BookmakerCatalogResponse":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/BookmakerCatalogItem"}}}},"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."}}},"BookmakerCountriesCatalogResponse":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/BookmakerCountryCatalogItem"}}}},"BetOpportunity":{"type":"object","description":"A normalized betting opportunity. Strategy-specific fields may be present.","additionalProperties":true,"properties":{"id":{"type":"string"},"strategy":{"type":"string","enum":["pos_ev","arbitrage","middles","freebets","pos_ev_special"]},"event_id":{"type":"string","nullable":true},"bookmaker_name":{"type":"string","nullable":true},"selection_key":{"type":"string","nullable":true},"odds":{"type":"number","nullable":true},"fair_odds":{"type":"number","nullable":true},"ev":{"type":"number","nullable":true},"ev_multiplicative_method":{"type":"number","nullable":true},"ev_additive_method":{"type":"number","nullable":true},"ev_power_method":{"type":"number","nullable":true},"ev_shin_method":{"type":"number","nullable":true}}},"BetsSnapshotResponse":{"type":"object","required":["resume","items"],"properties":{"resume":{"type":"string","description":"JSON string mapping each requested strategy to its stream resume ID."},"items":{"type":"array","items":{"$ref":"#/components/schemas/BetOpportunity"}}}},"BetsStreamDelta":{"type":"object","required":["strategy","op","id"],"properties":{"strategy":{"type":"string"},"op":{"type":"string","enum":["upsert","delete"]},"id":{"type":"string"},"doc":{"$ref":"#/components/schemas/BetOpportunity"}}},"BetsStreamEvent":{"type":"object","required":["resume","events"],"properties":{"resume":{"type":"string"},"events":{"type":"array","items":{"$ref":"#/components/schemas/BetsStreamDelta"}}}},"OddsHistoryStreamEvent":{"type":"object","required":["event_id","selection_key","resume","points"],"properties":{"event_id":{"type":"string"},"selection_key":{"type":"string"},"resume":{"type":"string"},"points":{"type":"array","items":{"$ref":"#/components/schemas/OddsHistoryPoint"}}}},"RacingEventsStreamChange":{"type":"object","required":["op","event_id"],"properties":{"op":{"type":"string","enum":["upsert","remove"]},"event_id":{"type":"string"},"event":{"type":"object","nullable":true,"additionalProperties":true}}},"RacingEventsStreamEvent":{"type":"object","required":["resume","changes"],"properties":{"resume":{"type":"string"},"changes":{"type":"array","items":{"$ref":"#/components/schemas/RacingEventsStreamChange"}}}},"StreamHeartbeat":{"type":"object","description":"Heartbeat payload. All streams use it as a liveness signal; event-odds streams also include the latest event and bookmaker freshness metadata so a no-price-change capture is observable.","additionalProperties":false,"properties":{"event_id":{"type":"string","nullable":true},"resume":{"type":"string","nullable":true},"as_of_ts_ms":{"type":"integer","nullable":true},"snapshot_capture_ts_ms":{"type":"integer","nullable":true},"bookmaker_as_of_ts_ms":{"type":"object","additionalProperties":{"type":"integer"}},"oldest_bookmaker_as_of_ts_ms":{"type":"integer","nullable":true},"target_refresh_interval_seconds":{"type":"integer","nullable":true}}},"StreamResyncEvent":{"type":"object","description":"Sent when a resume token is no longer available and the client should reload the snapshot.","required":["reason"],"properties":{"event_id":{"type":"string","nullable":true},"selection_key":{"type":"string","nullable":true},"resume":{"type":"string","nullable":true},"reason":{"type":"string","example":"trimmed"},"snapshot_id":{"type":"string","nullable":true}}},"InPlayExchangeWebSocketMessage":{"type":"object","description":"Multiplexed in-play exchange WebSocket message.","required":["type"],"additionalProperties":true,"properties":{"type":{"type":"string","enum":["subscribed","unsubscribed","image","delta","heartbeat","pong","error"]},"event_id":{"type":"string","nullable":true},"resume":{"type":"string","nullable":true},"market_ids":{"type":"array","items":{"type":"string"}},"changes":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Send your API key in this header on every request."}}},"tags":[{"name":"Start here","description":"API identity, base URL, authentication, and reference links."},{"name":"Status","description":"Buyer-facing API availability, latency, stream health, and rate-limit health."},{"name":"Account","description":"Current API identity, usage counters, and contract limits."},{"name":"Catalog","description":"Supported sports, leagues, bookmakers, and approximate market coverage."},{"name":"Widgets","description":"Public-safe embeddable widget feeds for approved API clients."},{"name":"Sports events","description":"Upcoming and live sports events plus event metadata."},{"name":"Sports odds","description":"Sports odds snapshots, line movement, Server-Sent Events, and WebSocket updates."},{"name":"Sports exchange","description":"Sports betting exchange order books with back/lay ladders, liquidity, and live updates."},{"name":"Prediction markets","description":"Event-linked prediction-market order books with probability ladders, liquidity, and live updates."},{"name":"Racing events","description":"Racing event discovery and live racing event updates."},{"name":"Racing odds","description":"Racing odds snapshots, home racing feeds, Server-Sent Events, and WebSocket updates."},{"name":"Betting opportunities","description":"Positive EV, arbitrage, middle, and bonus-bet opportunity feeds."},{"name":"Results","description":"Sports event result lookup."}],"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":[]}]}