{"openapi":"3.1.0","info":{"title":"betspread Odds API","version":"1.0.0","description":"Real-time odds comparison data from European bookmakers. Covers multi-sport current odds through separate REST and Live product channels, filtered by organization entitlements and verified bookmaker product coverage. Sources marked source_limited or live_beta are not included in broad customer odds surfaces.","contact":{"email":"api@betspread.io"}},"servers":[{"url":"https://api.betspread.io/api/v1","description":"Production"}],"security":[{"bearerAuth":[]},{"apiKey":[]}],"paths":{"/sports":{"get":{"operationId":"listSports","summary":"List available sports","description":"Lists sports with at least one entitled bookmaker that has product-verified coverage for the requested transport. Broad sport lists exclude source_limited and live_beta sources.","tags":["Reference Data"],"parameters":[{"$ref":"#/components/parameters/Transport"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of sports","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Sport"}}}}]}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/events":{"get":{"operationId":"listEvents","summary":"List events with optional filters","description":"Lists events with current odds from entitled bookmakers that have product-verified coverage for the requested transport. Broad event queries exclude source_limited and live_beta sources.","tags":["Events"],"parameters":[{"name":"sport","in":"query","schema":{"type":"string","enum":["football","tennis","basketball","ice_hockey","handball","baseball","darts","australian_rules","rugby","cricket","combat_sport","volleyball","american_football","golf","motorsport","cycling","table_tennis","snooker","badminton","esports","waterpolo","winter_sport"]},"description":"Filter by sport slug"},{"name":"league","in":"query","schema":{"type":"string"},"description":"Filter by league key (e.g. `bundesliga`)"},{"name":"dateFrom","in":"query","schema":{"type":"string","format":"date-time"},"description":"Earliest kickoff (ISO-8601); must be before or equal to dateTo when both are provided"},{"name":"dateTo","in":"query","schema":{"type":"string","format":"date-time"},"description":"Latest kickoff (ISO-8601); must be after or equal to dateFrom when both are provided"},{"$ref":"#/components/parameters/Transport"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of events","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}}}}]}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/events/{eventId}/odds":{"get":{"operationId":"getEventOdds","summary":"Get odds for a specific event","description":"Returns all available odds grouped by market type, with bookmaker odds nested per market. Results require entitlement and product-verified coverage for the requested transport; source_limited and live_beta sources are not returned from broad customer odds surfaces.","tags":["Odds"],"parameters":[{"name":"eventId","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9]+$"},"description":"Numeric event id from `/events`. Non-numeric values return `400 validation_failed`; well-formed but unknown ids return `404 not_found`."},{"name":"bookmaker","in":"query","style":"form","explode":false,"schema":{"type":"array","minItems":1,"uniqueItems":true,"items":{"type":"string","minLength":1},"example":["tipico","bwin"]},"description":"Optional comma-separated bookmaker slug filter. Blank or duplicate entries are rejected. Results still require entitlement and product-verified coverage for the requested transport."},{"$ref":"#/components/parameters/Transport"}],"responses":{"200":{"description":"Odds for the event","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/EventOdds"}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/bookmakers":{"get":{"operationId":"listBookmakers","summary":"List all available bookmakers","description":"Lists entitled bookmakers with product-verified coverage for the requested transport. Broad bookmaker lists exclude source_limited and live_beta sources.","tags":["Reference Data"],"parameters":[{"name":"sport","in":"query","schema":{"type":"string","enum":["football","tennis","basketball","ice_hockey","handball","baseball","darts","australian_rules","rugby","cricket","combat_sport","volleyball","american_football","golf","motorsport","cycling","table_tennis","snooker","badminton","esports","waterpolo","winter_sport"],"default":"football"},"description":"Filter by sport slug (default: football)"},{"$ref":"#/components/parameters/Transport"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of bookmakers","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Bookmaker"}}}}]}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/spec":{"get":{"operationId":"getSpec","summary":"Download the OpenAPI 3.1 contract for the v1 API","description":"Returns the same machine-readable OpenAPI 3.1 specification that powers the developer portal at `/docs`. This endpoint is public — no API key is required — and is served with `Access-Control-Allow-Origin: *` and `Cache-Control: public, max-age=300` so client generators and third-party API tools can fetch it directly.","tags":["Reference Data"],"security":[],"parameters":[],"responses":{"200":{"description":"OpenAPI 3.1 specification document for this API","content":{"application/json":{"schema":{"type":"object","description":"OpenAPI 3.1 specification object. See https://spec.openapis.org/oas/v3.1.0 for the full schema."}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Bearer API key obtained from the betspread dashboard."},"apiKey":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key obtained from the betspread dashboard."}},"parameters":{"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"description":"Maximum number of results per page"},"Offset":{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0},"description":"Number of results to skip"},"Transport":{"name":"transport","in":"query","schema":{"type":"string","enum":["rest","live"],"default":"rest"},"description":"Single product channel to query. `rest` and `live` are API products, not event phase labels."}},"schemas":{"Sport":{"type":"object","required":["slug","name","coverage"],"properties":{"slug":{"type":"string","enum":["football","tennis","basketball","ice_hockey","handball","baseball","darts","australian_rules","rugby","cricket","combat_sport","volleyball","american_football","golf","motorsport","cycling","table_tennis","snooker","badminton","esports","waterpolo","winter_sport"],"example":"football"},"name":{"type":"string","example":"Football"},"coverage":{"$ref":"#/components/schemas/SportProductCoverage"}}},"SportProductCoverage":{"type":"object","required":["transport","bookmakerCount","bookmakers"],"properties":{"transport":{"type":"string","enum":["rest","live"],"example":"rest"},"bookmakerCount":{"type":"integer","minimum":1,"example":3},"bookmakers":{"type":"array","items":{"$ref":"#/components/schemas/SportCoverageBookmaker"}}}},"SportCoverageBookmaker":{"type":"object","required":["slug","displayName","coverageStatus","restAvailable","liveAvailable","lastVerifiedAt"],"properties":{"slug":{"type":"string","example":"tipico"},"displayName":{"type":"string","example":"Tipico"},"coverageStatus":{"type":"string","enum":["verified_rest_live","verified_rest_only","verified_live_only","live_beta","source_limited","disabled_until_review"],"example":"verified_rest_live"},"restAvailable":{"type":"boolean","example":true},"liveAvailable":{"type":"boolean","example":true},"lastVerifiedAt":{"type":["string","null"],"format":"date","example":"2026-05-20","description":"Date when source coverage was last verified end-to-end. `null` until a verification run has been recorded."}}},"Bookmaker":{"type":"object","required":["slug","displayName","coverage"],"properties":{"slug":{"type":"string","example":"betano"},"displayName":{"type":"string","example":"Betano"},"coverage":{"$ref":"#/components/schemas/BookmakerProductCoverage"}}},"BookmakerProductCoverage":{"type":"object","required":["region","countries","restAvailable","liveAvailable","coverageStatus","sourceMode","lastVerifiedAt","customerNote"],"properties":{"region":{"type":"string","example":"Germany / DACH"},"countries":{"type":"array","items":{"type":"string"},"example":["DE"]},"restAvailable":{"type":"boolean","example":true},"liveAvailable":{"type":"boolean","example":true},"coverageStatus":{"type":"string","enum":["verified_rest_live","verified_rest_only","verified_live_only","live_beta","source_limited","disabled_until_review"],"example":"verified_rest_live"},"sourceMode":{"type":"string","enum":["direct","worker_proxy","sidecar_proxy","cdp_proxy","credential_required","disabled"],"example":"direct"},"lastVerifiedAt":{"type":["string","null"],"format":"date","example":"2026-05-20","description":"Date when source coverage was last verified end-to-end. `null` until a verification run has been recorded."},"customerNote":{"type":"string","example":"Verified REST and STOMP live output."}}},"Event":{"type":"object","required":["id","sport","leagueKey","leagueName","homeTeam","awayTeam","kickoffAt","marketCount"],"properties":{"id":{"type":"string","example":"42891"},"sport":{"type":"string","enum":["football","tennis","basketball","ice_hockey","handball","baseball","darts","australian_rules","rugby","cricket","combat_sport","volleyball","american_football","golf","motorsport","cycling","table_tennis","snooker","badminton","esports","waterpolo","winter_sport"],"example":"football"},"leagueKey":{"type":"string","example":"bundesliga"},"leagueName":{"type":"string","example":"Bundesliga"},"homeTeam":{"type":"string","example":"Bayern München"},"awayTeam":{"type":"string","example":"Borussia Dortmund"},"kickoffAt":{"type":"string","format":"date-time","example":"2026-05-03T15:30:00.000Z"},"marketCount":{"type":"integer","example":4}}},"OutcomeOdds":{"type":"object","required":["outcome","transport","odds","status","lastUpdatedAt"],"properties":{"outcome":{"type":"string","example":"home"},"transport":{"type":"string","enum":["rest","live"],"example":"rest"},"odds":{"type":"number","format":"double","example":2.15},"status":{"type":"string","enum":["open","suspended","closed"],"example":"open"},"lastUpdatedAt":{"type":"string","format":"date-time","example":"2026-05-03T14:22:10.000Z"}}},"BookmakerOdds":{"type":"object","required":["slug","displayName","outcomes"],"properties":{"slug":{"type":"string","example":"betano"},"displayName":{"type":"string","example":"Betano"},"outcomes":{"type":"array","items":{"$ref":"#/components/schemas/OutcomeOdds"}}}},"MarketOdds":{"type":"object","required":["type","bookmakers"],"properties":{"type":{"type":"string","enum":["match_result_1x2","double_chance","total_goals","btts","match_winner","total","spread","first_half_total_goals","second_half_total_goals","correct_score","halftime_fulltime","odd_even","corners_total","cards_total","goalscorer","player_prop","shots_total","first_to_score","winning_margin","clean_sheet","race_to","outright","map_winner","set_betting","fight_betting","run_line","puck_line","quarter_betting"],"example":"match_result_1x2"},"line":{"type":["number","null"],"example":null,"description":"Market line (e.g. 2.5 for Over/Under). Null for line-less markets like 1X2."},"bookmakers":{"type":"array","items":{"$ref":"#/components/schemas/BookmakerOdds"}}}},"EventOdds":{"type":"object","required":["event","markets"],"properties":{"event":{"$ref":"#/components/schemas/Event"},"markets":{"type":"array","items":{"$ref":"#/components/schemas/MarketOdds"}}}},"PaginationMeta":{"type":"object","required":["total","limit","offset","transport"],"properties":{"total":{"type":"integer","example":142},"limit":{"type":"integer","example":50},"offset":{"type":"integer","example":0},"transport":{"type":"string","enum":["rest","live"],"example":"rest","description":"Resolved product channel used by the query. This is not an event phase label."},"sport":{"type":"string","enum":["football","tennis","basketball","ice_hockey","handball","baseball","darts","australian_rules","rugby","cricket","combat_sport","volleyball","american_football","golf","motorsport","cycling","table_tennis","snooker","badminton","esports","waterpolo","winter_sport"],"example":"football","description":"Resolved sport filter when the route is sport-scoped. Omitted for sport discovery."}}},"PaginationEnvelope":{"type":"object","required":["meta"],"properties":{"meta":{"$ref":"#/components/schemas/PaginationMeta"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","example":"validation_failed"},"message":{"type":"string","example":"limit must be between 1 and 500"}}}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key. Supply `Authorization: Bearer <key>` (recommended) or `X-Api-Key: <key>`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missingApiKey":{"summary":"No credential supplied","value":{"error":{"code":"missing_api_key","message":"Provide an API key via Authorization: Bearer or X-Api-Key"}}},"invalidApiKey":{"summary":"Credential is rejected","value":{"error":{"code":"invalid_api_key","message":"API key is invalid, expired, or revoked"}}}}}}},"ValidationError":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"validationFailed":{"summary":"Query or path parameter failed validation","value":{"error":{"code":"validation_failed","message":"sport must be one of: football, tennis, basketball, ..."}}}}}}},"Forbidden":{"description":"Request is authenticated but not entitled. `transport=live` requires `plan_limits.live_enabled=true` and can return `live_not_enabled`; odds detail requests can also return `forbidden` when no product-verified bookmaker entitlement exists for the requested sport and transport; a restricted API key can return `api_key_scope_denied` when its key scope does not allow the requested product channel, bookmaker, or sport.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"liveNotEnabled":{"summary":"Live product not enabled","value":{"error":{"code":"live_not_enabled","message":"Live transport requires plan access"}}},"apiKeyScopeDenied":{"summary":"Restricted API key scope denied","value":{"error":{"code":"api_key_scope_denied","message":"API key scope does not allow this product channel, bookmaker, or sport"}}},"forbidden":{"summary":"No product-verified entitlement","value":{"error":{"code":"forbidden","message":"No product-verified bookmaker entitlements available for this sport and transport"}}}}}}},"NotFound":{"description":"Numeric eventId is well-formed but the event does not exist (or is not visible to this organization).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"eventNotFound":{"summary":"Event id is unknown","value":{"error":{"code":"not_found","message":"Event not found"}}}}}}},"RateLimited":{"description":"Rate limit or monthly quota exceeded","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed in the active hourly rate-limit or monthly quota window"},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests remaining in the active hourly rate-limit or monthly quota window"},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix timestamp when the active rate-limit or quota window resets"},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the next request is allowed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rateLimitExceeded":{"summary":"Hourly rate limit exhausted","value":{"error":{"code":"rate_limit_exceeded","message":"Rate limit exceeded. Upgrade your plan for higher limits."}}},"monthlyQuotaExceeded":{"summary":"Monthly REST quota exhausted","value":{"error":{"code":"rate_limit_exceeded","message":"Monthly REST request quota exceeded. Upgrade your plan for higher limits."}}}}}}},"ServiceUnavailable":{"description":"Rate-limit backend unavailable (fail-closed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rateLimitBackendUnavailable":{"summary":"Rate-limit backend unavailable","value":{"error":{"code":"service_unavailable","message":"Rate-limit backend unavailable. Try again shortly."}}},"monthlyQuotaBackendUnavailable":{"summary":"Monthly quota backend unavailable","value":{"error":{"code":"service_unavailable","message":"Monthly quota backend unavailable. Try again shortly."}}}}}}}}}}