# Odds-API.io Complete Documentation # Full API documentation for AI tools with large context windows # Last updated: April 2026 (added dropping-odds endpoint) ================================================================================ OVERVIEW ================================================================================ Odds-API.io is a sports betting odds comparison API providing real-time data from 265 bookmakers across 34 sports with near-zero latency. Key Features: - 265+ bookmakers supported - 34 sports coverage - Real-time odds with close to zero latency - Pre-match & In-play odds coverage - Period scores for all periods (half-time, 90min, overtime) - enabling complete market settlement - Value bet detection - Arbitrage opportunity detection - WebSocket real-time updates - Direct bet links to bookmaker sites Base URL: https://api.odds-api.io/v3 ================================================================================ AUTHENTICATION ================================================================================ Odds-API.io uses API key authentication. Include your API key as a query parameter: https://api.odds-api.io/v3/events?apiKey=YOUR_API_KEY&sport=football Getting Your API Key: 1. Sign up for free at https://odds-api.io 2. Check your email for the API key 3. Store it securely in environment variables Security Best Practices: - Never expose API keys in client-side code - Use environment variables - Make API calls from backend servers only Rate Limits: - 5,000 requests/hour on all plans - Additional packages available: +10K/20K/30K requests/hour - Rate limit headers returned with every response: - x-ratelimit-limit: Total requests allowed - x-ratelimit-remaining: Requests remaining - x-ratelimit-reset: ISO 8601 timestamp when limit resets HTTP Status Codes: - 200: Success - 400: Bad Request - Invalid parameters - 401: Unauthorized - Invalid or missing API key - 403: Forbidden - API key doesn't have access - 404: Not Found - Resource doesn't exist - 429: Too Many Requests - Rate limit exceeded - 500: Internal Server Error ================================================================================ API ENDPOINTS ================================================================================ ------------------------------------------------------------------------------ GET /sports ------------------------------------------------------------------------------ Returns list of available sports. No authentication required. Response: [ {"name": "Football", "slug": "football"}, {"name": "Basketball", "slug": "basketball"}, ... ] ------------------------------------------------------------------------------ GET /bookmakers ------------------------------------------------------------------------------ Returns list of available bookmakers. No authentication required. Response: [ {"name": "Bet365", "active": true}, {"name": "SingBet", "active": true}, ... ] ------------------------------------------------------------------------------ GET /leagues ------------------------------------------------------------------------------ Returns leagues for a specified sport. Parameters: - apiKey (required): Your API key - sport (required): Sport slug (e.g., "football") Example: /leagues?apiKey=YOUR_KEY&sport=football Response: [ {"name": "England - Premier League", "slug": "england-premier-league", "eventsCount": 32}, {"name": "Spain - La Liga", "slug": "spain-la-liga", "eventsCount": 28}, ... ] ------------------------------------------------------------------------------ GET /events ------------------------------------------------------------------------------ Returns events with filtering options. Parameters: - apiKey (required): Your API key - sport (required): Sport slug - league (optional): League slug - status (optional): Comma-separated statuses (pending, live, settled) - from (optional): Start date/time in RFC3339 format - to (optional): End date/time in RFC3339 format. If omitted, defaults to 14 days from now (see "Default window" below). - limit (optional): Max events to return. Capped at 5000 per response. - skip (optional): Offset for pagination (use with limit). - bookmaker (optional): Filter events that have odds from this bookmaker - participantId (optional): Filter by team/participant ID Default window: Events are listed across a wide future horizon, so when no `to` is given the response defaults to events within the next 14 days. Pass an explicit `to` (and optionally `from`) to widen or shift the window. Every response is hard-capped at 5000 events - paginate with `limit` + `skip` for larger ranges. Example: /events?apiKey=YOUR_KEY&sport=football&status=pending,live Example: /events?apiKey=YOUR_KEY&sport=football&to=2026-09-01T00:00:00Z (full horizon) Response: [ { "id": 123456, "home": "Manchester United", "away": "Liverpool", "homeId": 38, "awayId": 42, "date": "2025-10-15T15:00:00Z", "status": "pending", "sport": {"name": "Football", "slug": "football"}, "league": {"name": "England - Premier League", "slug": "england-premier-league"}, "scores": { "home": 2, "away": 1, "periods": { "p1": {"home": 1, "away": 0}, "ft": {"home": 2, "away": 1} } } } ] Note on Scores: The scores object contains the final result (home/away) plus a "periods" map with the score for each period. The top-level home/away is the OT/penalty-inclusive result. Period keys that may appear: - "p1", "p2", ... "pN" - period/set/quarter scores (halves, sets, quarters, etc.) - "ft" - full-time / regulation result - "ot" - overtime / extra time / extra innings (when played) - "ap" - penalty shootout tally (when the match is decided on penalties) - "currentgame" - current game points, live tennis - "map1", "map2", ... - per-map scores, esports - "inning1", "inning2", ... - per-innings runs, cricket Which keys appear depends on sport and how the match resolved. This enables complete settlement of all market types (half-time, period-specific, OT/penalties, and more). Note: the full-time key is "ft" (the legacy feed used "fulltime"). Cricket periods carry two extra fields alongside home/away runs: "periods": { "inning1": { "home": 155, "away": 125, "wickets": {"home": 6, "away": 9}, "overs": {"home": 20, "away": 20} } } - "wickets" - wickets lost by each side in that innings (integer pair) - "overs" - overs bowled, cricket notation: 19.3 means 19 overs and 3 balls Both are cricket-only and are omitted for every other sport, so existing period scores keep exactly their {home, away} shape. Both are also omitted when the source does not publish them, so treat them as optional even on cricket. A side that has not yet batted reads 0 rather than a missing key. Cricket has no match clock, so live cricket events carry no "clock" object. ------------------------------------------------------------------------------ GET /events/live ------------------------------------------------------------------------------ Returns all currently live events. Parameters: - apiKey (required): Your API key - sport (optional): Filter by sport slug Example: /events/live?apiKey=YOUR_KEY&sport=football Live events include an optional "clock" object with the current match clock: { "clock": { "minute": 67, // current match minute "playedSeconds": 4023, // seconds of play elapsed "period": 2, // current period (half, quarter, set) "running": true, // whether the clock is ticking "statusDetail": "2nd half", "serve": "home", // current server, or player at the table (snooker, darts) "injuryTime": 4 // announced stoppage minutes (football) } } "clock" also appears on live events returned by GET /events?status=live and GET /events/{id}. It is omitted when no clock data is available. minute/playedSeconds are extrapolated server-side and only advance while running is true; extrapolate client-side from playedSeconds for a per-second UI ticker. statusDetail is the feed's phase description passed through as-is (display text, not an enum; use period + running for logic). Typical values per sport: football "1st half"/"Halftime"/"2nd half"/"Penalties"; basketball "1st quarter".."4th quarter"; tennis/volleyball "1st set"/"2nd set"/...; esports "Map 1"/"Map 2"/...; baseball "1st inning".."9th inning" (untimed, no clock seconds); snooker/darts "Started". minute is cumulative elapsed play. To render football's conventional "45+X"/"90+X" style, cap at the period boundary: period 1 with minute 48 displays as "45+3". injuryTime carries the announced stoppage minutes. ------------------------------------------------------------------------------ GET /events/search ------------------------------------------------------------------------------ Search events by team or league name. Returns up to 10 results ranked by relevance. Matching is case- and accent-insensitive. Parameters: - apiKey (required): Your API key - query (required): Search term (min 3 characters) - status (optional): upcoming (default) searches events that have not started; live searches in-play events and returns current scores Example: /events/search?apiKey=YOUR_KEY&query=Liverpool Example: /events/search?apiKey=YOUR_KEY&query=Liverpool&status=live ------------------------------------------------------------------------------ GET /events/{id} ------------------------------------------------------------------------------ Get a single event by ID. Parameters: - apiKey (required): Your API key - id (required): Event ID in path Example: /events/123456?apiKey=YOUR_KEY ------------------------------------------------------------------------------ GET /odds ------------------------------------------------------------------------------ Get odds for a specific event from selected bookmakers. Parameters: - apiKey (required): Your API key - eventId (required): Event ID - bookmakers (required): Comma-separated bookmaker names (max 30) Example: /odds?apiKey=YOUR_KEY&eventId=123456&bookmakers=Bet365,SingBet,Unibet Response: { "id": 123456, "home": "Manchester United", "away": "Liverpool", "date": "2025-10-15T15:00:00Z", "status": "pending", "sport": {"name": "Football", "slug": "football"}, "league": {"name": "England - Premier League", "slug": "england-premier-league"}, "urls": { "Bet365": "https://bet365.com/...", "SingBet": "https://SingBet.com/..." }, "bookmakers": { "Bet365": [ { "name": "ML", "updatedAt": "2025-10-04T10:30:00Z", "odds": [ {"home": "2.10", "draw": "3.40", "away": "3.20"} ] }, { "name": "Spread", "updatedAt": "2025-10-04T10:30:00Z", "odds": [ {"hdp": -0.5, "home": "1.95", "away": "1.85"}, {"hdp": 0, "home": "1.75", "away": "2.05"} ] }, { "name": "Totals", "updatedAt": "2025-10-04T10:30:00Z", "odds": [ {"hdp": 2.5, "over": "1.90", "under": "1.90"} ] } ] } } Market Types (pass these exact names to the `markets` parameter; matching is case-insensitive): - ML: Match result (home, draw, away) - Spread: Handicap with hdp value, including Asian lines - Totals: Totals with hdp as the line - Both Teams To Score: yes/no markets - Correct Score: Exact score predictions - Double Chance, Draw No Bet, European Handicap, Half Time / Full Time - Team Total Home / Team Total Away: totals for one side only - Half-time and period variants use a suffix: ML HT, Totals HT, Spread HT, Totals 2H - Sport-specific: Totals (Games) and Spread (Games) for tennis, Player Props for basketball - And many more... Common mistakes: there is no market named "Match Winner" (use ML), and no bare "Over/Under" or "Asian Handicap". Note that longer names containing those words do exist, such as "Goals Over/Under" and "Alternative Asian Handicap", so match the full name exactly. ------------------------------------------------------------------------------ GET /odds/multi ------------------------------------------------------------------------------ Get odds for multiple events in a single request (counts as 1 API call). Parameters: - apiKey (required): Your API key - eventIds (required): Comma-separated event IDs (max 10) - bookmakers (required): Comma-separated bookmaker names (max 30) Example: /odds/multi?apiKey=YOUR_KEY&eventIds=123456,123457,123458&bookmakers=Bet365,SingBet Response: Array of event odds objects (same format as /odds) ------------------------------------------------------------------------------ GET /odds/updated ------------------------------------------------------------------------------ Get odds updated since a specific timestamp. Parameters: - apiKey (required): Your API key - since (required): Unix timestamp (max 90 seconds old) - bookmaker (required): Bookmaker name (any name from /bookmakers; the response uses the same name) - sport (required): Sport name Example: /odds/updated?apiKey=YOUR_KEY&since=1734450000&bookmaker=SingBet&sport=Football ------------------------------------------------------------------------------ GET /odds/movements ------------------------------------------------------------------------------ Get historical odds movements for a specific market. History is recorded only for a fixed set of bookmakers: Bet365, 12bet, DraftKings, FanDuel, FB Sports, GG.bet, M88, Norsk Tipping, ON Sharp, Sbobet, SingBet, Superbet, Veikkaus, and selected sharp books. Requesting a bookmaker outside this set returns a 404. Parameters: - apiKey (required): Your API key - eventId (required): Event ID - bookmaker (required): Bookmaker name - market (required): Market name (e.g., "ML", "Totals") - marketLine (optional): Handicap line (required for non-ML markets) Example: /odds/movements?apiKey=YOUR_KEY&eventId=123456&bookmaker=SingBet&market=ML Response: { "eventid": "123456", "bookmaker": "SingBet", "opening": {"home": 2.0, "draw": 3.2, "away": 3.5, "timestamp": 1734400000}, "latest": {"home": 2.1, "draw": 3.3, "away": 3.4, "timestamp": 1734450000}, "movements": [ {"home": 2.0, "draw": 3.2, "away": 3.5, "timestamp": 1734400000}, {"home": 2.05, "draw": 3.25, "away": 3.45, "timestamp": 1734420000}, {"home": 2.1, "draw": 3.3, "away": 3.4, "timestamp": 1734450000} ] } ------------------------------------------------------------------------------ GET /value-bets ------------------------------------------------------------------------------ Get value betting opportunities calculated by comparing bookmaker odds against market consensus. Parameters: - apiKey (required): Your API key - bookmaker (required): Bookmaker name - sport (optional): Filter by sport slug (e.g. baseball). Server-side filter. - league (optional): Filter by league slug (e.g. usa-mlb). Requires sport to also be set. - includeEventDetails (optional): Include event info (true/false) Validation: - league without sport -> 400 - Unknown sport slug -> 400 - Unknown league for a valid sport -> 404 Examples: /value-bets?apiKey=YOUR_KEY&bookmaker=Bet365&includeEventDetails=true /value-bets?apiKey=YOUR_KEY&bookmaker=Bet365&sport=baseball /value-bets?apiKey=YOUR_KEY&bookmaker=Bet365&sport=baseball&league=usa-mlb Response: [ { "id": "vb_123456_bet365_home", "eventId": 123456, "bookmaker": "Bet365", "market": { "name": "ML", "home": "2.10", "draw": "3.40", "away": "3.20" }, "betSide": "home", "expectedValue": 0.0523, "expectedValueUpdatedAt": "2025-10-04T10:30:00Z", "bookmakerOdds": { "home": "2.10", "draw": "3.40", "away": "3.20", "homeDirectLink": "https://bet365.com/..." }, "event": { "home": "Manchester United", "away": "Liverpool", "date": "2025-10-15T15:00:00Z", "sport": "Football", "league": "England - Premier League" } } ] Expected Value (EV) = (Probability × Odds) - 1 Positive EV indicates a value bet. ------------------------------------------------------------------------------ GET /arbitrage-bets ------------------------------------------------------------------------------ Get arbitrage betting opportunities across bookmakers. Parameters: - apiKey (required): Your API key - bookmakers (required): Comma-separated bookmaker names - limit (optional): Max results (default 50, max 500) - includeEventDetails (optional): Include event info (true/false) Example: /arbitrage-bets?apiKey=YOUR_KEY&bookmakers=Bet365,SingBet&includeEventDetails=true Response: [ { "id": "arb_123456_ml", "eventId": 123456, "market": {"name": "ML", "hdp": null}, "profitMargin": 0.0215, "impliedProbability": 0.9785, "totalStake": 100, "legs": [ {"side": "home", "bookmaker": "Bet365", "odds": "2.15", "directLink": "..."}, {"side": "away", "bookmaker": "SingBet", "odds": "2.10", "directLink": "..."} ], "optimalStakes": [ {"side": "home", "bookmaker": "Bet365", "stake": 48.84, "potentialReturn": 105.00}, {"side": "away", "bookmaker": "SingBet", "stake": 51.16, "potentialReturn": 107.44} ], "event": {...} } ] ------------------------------------------------------------------------------ GET /dropping-odds ------------------------------------------------------------------------------ Get dropping odds from sharp bookmakers. Tracks significant odds movements to identify where the market is moving before recreational books adjust. Requires a paid plan (Starter or above). Free plan users receive 403 Forbidden. Parameters: - apiKey (required): Your API key - sport (optional): Sport slug (e.g., "football") - league (optional): League slug (requires sport). e.g., "england-premier-league". Mutually exclusive with leagues. - leagues (optional): Comma-separated league slugs to filter by multiple leagues. e.g., "england-premier-league,spain-la-liga". Mutually exclusive with league. - markets (optional): Comma-separated market names to filter by (case-insensitive). e.g., "Spread,Totals". Supported: ML, Spread, Totals, Spread HT, Totals HT, Totals 1Q, Spread 1Q, Team Total Home, Team Total Away, First 5 Innings ML, First 5 Innings Spread, First 5 Innings Totals, Corners Spread, Corners Totals, Corners Spread HT, Corners Totals HT, Bookings Spread, Bookings Totals, Player Props - timeWindow (optional): Time window for drop filtering/sorting: "opening", "12h", "24h", "48h" (default "opening") - sort (optional): Sort order: "drop" (highest %), "recent" (latest movement), "kickoff" (soonest event). Default "drop" - minDrop (optional): Minimum drop percentage (default 0) - limit (optional): Results per page, max 200 (default 50) - page (optional): Page number, 1-indexed (default 1) - includeEventDetails (optional): Include event info like teams, date, sport, league (true/false, default false) Example: /dropping-odds?apiKey=YOUR_KEY&sport=football&minDrop=10&includeEventDetails=true Response: [ { "eventId": 69455182, "event": { "home": "Manchester City", "away": "Arsenal", "date": "2025-10-15T15:00:00Z", "sport": {"name": "Football", "slug": "football"}, "league": {"name": "England - Premier League", "slug": "england-premier-league"} }, "market": {"name": "Spread", "hdp": -1.5}, "betSide": "home", "odds": { "opening": 2.05, "current": 1.52, "drop": { "sinceOpening": 25.9, "12h": 8.4, "24h": 12.3, "48h": null } }, "lastMovedAt": 1775851240182, "updatedAt": 1775854172851 }, { "eventId": 61301179, "market": {"name": "Player Passes", "hdp": 34.5, "label": "Carlos Baleba"}, "betSide": "over", "odds": { "opening": 3.75, "current": 1.5, "drop": {"sinceOpening": 60, "12h": null, "24h": null, "48h": null} }, "lastMovedAt": 1776281915401, "updatedAt": 1776283503652 } ] Note: The "event" field is only included when includeEventDetails=true. Note: market.label contains the player name for player prop markets (e.g. "Carlos Baleba"). Omitted for non-player-prop markets. Note: market=Player Props returns all player prop markets across all sports. For football this includes sport-specific names like "Anytime Goalscorer", "Player Passes", "Player Shots", "Player Shots on Target", etc. Pagination headers returned with every response: - X-Total-Count: Total number of matching entries - X-Page: Current page number - X-Per-Page: Results per page - X-Updated-At: Timestamp of last data update (Unix milliseconds) Drop values: - sinceOpening: Total drop since opening odds were first recorded - 12h/24h/48h: Drop in the last 12/24/48 hours (null if no data for that window) ------------------------------------------------------------------------------ GET /participants ------------------------------------------------------------------------------ Get teams/participants for a sport. Parameters: - apiKey (required): Your API key - sport (required): Sport slug - search (optional): Filter by name Example: /participants?apiKey=YOUR_KEY&sport=football&search=Chelsea Response: [ {"id": 45, "name": "Chelsea", "sport": "football"}, {"id": 78, "name": "Chelsea Women", "sport": "football"} ] ------------------------------------------------------------------------------ GET /participants/{id} ------------------------------------------------------------------------------ Get participant by ID. Example: /participants/45?apiKey=YOUR_KEY ------------------------------------------------------------------------------ GET /participants/{id}/logo ------------------------------------------------------------------------------ Returns the team/participant crest as a PNG image, served from the Odds-API domain (no third-party URLs exposed). Responses carry Cache-Control: public, max-age=86400, so the URL can be hotlinked in tags. Returns 404 when no logo exists for the participant. Use homeId/awayId from event responses as the participant id. Example: /participants/45/logo?apiKey=YOUR_KEY ------------------------------------------------------------------------------ GET /bookmakers/selected ------------------------------------------------------------------------------ Get bookmakers selected by the user. Example: /bookmakers/selected?apiKey=YOUR_KEY ------------------------------------------------------------------------------ PUT /bookmakers/selected/select ------------------------------------------------------------------------------ Add bookmakers to user's selection. Parameters: - apiKey (required): Your API key - bookmakers (required): Comma-separated bookmaker names Example: /bookmakers/selected/select?apiKey=YOUR_KEY&bookmakers=Bet365,SingBet ------------------------------------------------------------------------------ PUT /bookmakers/selected/clear ------------------------------------------------------------------------------ Clear all selected bookmakers. Rate limited to once every 12 hours; the cooldown may be shorter for newly created accounts. Example: /bookmakers/selected/clear?apiKey=YOUR_KEY ================================================================================ WEBSOCKET API ================================================================================ Real-time odds updates via WebSocket connection. Endpoint: wss://api.odds-api.io/v3/ws?apiKey=YOUR_API_KEY&markets=ML,Spread,Totals Parameters: - apiKey (required): Your API key - markets (required for the odds channel): Comma-separated market names (max 20). Example: ML,Spread,Totals - channels (optional): Comma-separated subscription channels. Default is "odds" (existing behavior). Options: odds, scores, status. Example: channels=odds,scores,status - sport (optional): Comma-separated sport slugs (max 10). Example: football,basketball - leagues (optional): Comma-separated league slugs (max 20). Example: england-premier-league - eventIds (optional): Comma-separated event IDs (max 50). Example: 12345,67890 - status (optional): "live" or "prematch" - lastSeq (optional): Resume from this sequence number. The server replays missed messages before resuming the live stream. IMPORTANT: - The markets parameter is required for the odds channel - leagues and eventIds cannot be used together - Using leagues or eventIds is recommended to reduce bandwidth - channels defaults to "odds": omit it to keep the existing odds-only stream unchanged Channels: - odds (default): live odds/markets. Message types: created, updated, deleted, no_markets. - scores: live score changes. Message type: score. - status: match status transitions (pending, live, settled, cancelled). Message type: status. Subscribe to any combination, e.g. channels=scores,status for a scoreboard without odds traffic. IMPORTANT: channels is an allowlist - you receive ONLY the channels you list. To keep receiving odds you must include odds (e.g. channels=odds,scores,status); channels=scores,status stops odds delivery. Omitting channels entirely keeps the default odds-only stream. sport, leagues, eventIds and status filters apply to score/status messages too. Example URLs: wss://api.odds-api.io/v3/ws?apiKey=xxx&markets=ML,Spread,Totals&sport=football&status=live wss://api.odds-api.io/v3/ws?apiKey=xxx&markets=ML&leagues=england-premier-league,spain-la-liga wss://api.odds-api.io/v3/ws?apiKey=xxx&markets=ML,Totals&eventIds=12345,67890 Message Types: - welcome: Connection confirmed (includes active filters) - created: New match added (odds channel) - updated: Match or market changed (odds channel) - deleted: Match removed (odds channel) - no_markets: Match exists but no markets available (odds channel) - deleted and no_markets carry an empty markets array and are always delivered, even with a markets= filter. - deleted and no_markets DO respect status=, sport= and leagues= filters and carry the event date, so a pre-match removal is not delivered on a status=live connection. - no_markets means the bookmaker has no active markets for the event at all, not that none of your subscribed markets matched. - score: Live score changed (scores channel) - status: Match status changed to pending (new match added)/live/settled/cancelled (status channel) - resync_required: Replay cannot be served safely. Client must rebuild state from a REST snapshot and reconnect. Replay & Resync: - Each update message includes a globally increasing seq. Persist it client-side and reconnect with lastSeq. - Replay is compacted latest-state replay per eventId:bookie, not every intermediate tick. - deleted and no_markets states are replayed as the latest known state within retention. - If replay is too large or one or more replay payloads have expired, the server pushes current state as `created` updates and continues live on the same connection; resync_required is sent when complete state cannot be guaranteed (nothing cached, or reason snapshot_truncated), in which case refetch from REST and discard your stored last_seq; it is also sent with reason snapshot_cooldown when you keep reconnecting with the same last_seq you were already served a snapshot for; nothing is replayed then, so rebuild from REST like any other resync_required rather than adopting current_seq. - Recovery path: fetch /odds or /odds/multi with includeSeq=true, read the X-OddsAPI-Seq response header, then reconnect with lastSeq set to that value. Welcome Message (sent on connection): { "type": "welcome", "message": "Connected to OddsAPI WebSocket", "user_id": "user123", "bookmakers": ["Bet365", "Unibet"], "sport_filter": ["Football"], "leagues_filter": ["england-premier-league"], "status_filter": "live", "market_filter": ["ML", "SPREAD", "TOTALS"], "channels": ["odds", "scores", "status"], "connected_at": "2026-01-15T21:00:00Z" } Update Message Format: { "type": "updated", "seq": 482917, "timestamp": 1723992773, "id": "63017989", "bookie": "SingBet", "url": "https://www.singbet.com/sports/football/match/63017989", "markets": [ { "name": "ML", "updatedAt": "2024-01-15T10:30:00Z", "odds": [{"home": "1.85", "draw": "3.25", "away": "2.10", "max": 500}] } ] } Score Message Format (scores channel) - scores only, no status field: { "type": "score", "timestamp": 1723992773, "id": "63017989", "scores": {"home": 2, "away": 1, "periods": {"p1": {"home": 1, "away": 0}}}, "clock": {"minute": 67, "playedSeconds": 4023, "period": 2, "running": true, "statusDetail": "2nd half"} } The optional "clock" snapshot (same shape as the clock field on GET /events/live) rides on live score and live-status messages when clock data is available, so a client can re-anchor its local ticker on every real event. Omitted on pending/settled/cancelled messages and on sports without a clock. The clock arrives only with an event, so a 0-0 match or one watched mid-half sends nothing until the next event: seed the initial clock with one GET /events/live on connect, then let the WebSocket re-anchor it. Status Message Format (status channel): { "type": "status", "timestamp": 1723992773, "id": "63017989", "status": "settled", "scores": {"home": 2, "away": 1, "periods": {"p1": {"home": 1, "away": 0}}} } Note on score/status: these carry no bookie and no markets - they describe the event itself. The status message includes the current scores, so a status-only subscriber gets the final result on settle. score/status messages are best-effort live state and carry no seq; they are not replayed on reconnect - fetch the current value from GET /events after reconnecting. Error Responses (400 Bad Request): - Too many sports. Maximum 10 allowed. - Too many leagues. Maximum 20 allowed. - Too many event IDs. Maximum 50 allowed. - Too many markets. Maximum 20 allowed. - Cannot use both 'leagues' and 'eventIds' filters together. - Invalid status filter. Use 'prematch' or 'live' - Invalid channel: X. Allowed: odds, scores, status Pricing: 2x the REST API price (add-on feature, subscribe at https://odds-api.io) Bookmakers: The WebSocket automatically sends updates for all bookmakers you have selected in your account. Manage bookmakers via PUT /bookmakers/selected/select endpoint. Replace, do not merge: every `updated` message carries the complete current market set for that event and bookmaker, not only the markets that changed. Overwrite your stored markets for that event with what arrives. Suspended markets are removed from the payload rather than flagged, so their absence is the signal. Merging incoming markets into existing state leaves suspended markets in your data permanently. ================================================================================ SUPPORTED SPORTS (34 total) ================================================================================ Football (football) Basketball (basketball) Tennis (tennis) Baseball (baseball) American Football (american-football) Ice Hockey (ice-hockey) Esports (esports) Darts (darts) MMA (mixed-martial-arts) Boxing (boxing) Handball (handball) Volleyball (volleyball) Snooker (snooker) Table Tennis (table-tennis) Rugby (rugby) Cricket (cricket) Waterpolo (water-polo) Futsal (futsal) Beach Volley (beach-volleyball) Aussie Rules (aussie-rules) Floorball (floorball) Squash (squash) Beach Soccer (beach-soccer) Lacrosse (lacrosse) Curling (curling) Padel (padel) Bandy (bandy) Gaelic Football (gaelic-football) Beach Handball (beach-handball) Athletics (athletics) Badminton (badminton) Cross-Country (cross-country) Golf (golf) Cycling (cycling) ================================================================================ CODE EXAMPLES ================================================================================ ------------------------------------------------------------------------------ JavaScript/Node.js - Fetch Events and Odds ------------------------------------------------------------------------------ const apiKey = process.env.ODDS_API_KEY; // Get football events const eventsResponse = await fetch( `https://api.odds-api.io/v3/events?apiKey=${apiKey}&sport=football` ); const events = await eventsResponse.json(); // Get odds for first event const eventId = events[0].id; const oddsResponse = await fetch( `https://api.odds-api.io/v3/odds?apiKey=${apiKey}&eventId=${eventId}&bookmakers=Bet365,SingBet` ); const odds = await oddsResponse.json(); // Find best home odds let bestHomeOdds = { bookmaker: null, odds: 0 }; for (const [bookmaker, markets] of Object.entries(odds.bookmakers)) { const ml = markets.find(m => m.name === 'ML'); if (ml && parseFloat(ml.odds[0].home) > bestHomeOdds.odds) { bestHomeOdds = { bookmaker, odds: parseFloat(ml.odds[0].home) }; } } console.log(`Best home odds: ${bestHomeOdds.odds} at ${bestHomeOdds.bookmaker}`); ------------------------------------------------------------------------------ Python - Fetch Value Bets ------------------------------------------------------------------------------ import requests import os api_key = os.environ['ODDS_API_KEY'] response = requests.get( 'https://api.odds-api.io/v3/value-bets', params={ 'apiKey': api_key, 'bookmaker': 'Bet365', 'includeEventDetails': True } ) value_bets = response.json() # Filter bets with > 3% expected value top_bets = [bet for bet in value_bets if bet['expectedValue'] >= 0.03] top_bets.sort(key=lambda x: x['expectedValue'], reverse=True) for bet in top_bets[:5]: print(f"{bet['event']['home']} vs {bet['event']['away']}") print(f"Bet on: {bet['betSide']}") print(f"EV: {bet['expectedValue'] * 100:.2f}%") print("---") ------------------------------------------------------------------------------ JavaScript - WebSocket Connection ------------------------------------------------------------------------------ const WebSocket = require('ws'); const apiKey = process.env.ODDS_API_KEY; const params = new URLSearchParams({ apiKey, markets: 'ML,Spread,Totals', sport: 'football', status: 'live' }); const ws = new WebSocket(`wss://api.odds-api.io/v3/ws?${params}`); let lastSeq = 0; ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'resync_required') { console.log(`Resync required: ${data.reason}`); console.log('Fetch a REST snapshot with includeSeq=true, rebuild local state, then reconnect with that seq.'); return; } if (data.seq) { lastSeq = data.seq; } switch (data.type) { case 'welcome': console.log(`Connected! Filters: ${data.sport_filter}, ${data.status_filter}`); break; case 'created': console.log(`New match: ${data.id}`); break; case 'updated': console.log(`Updated: ${data.id} - ${data.markets.length} markets`); break; case 'deleted': console.log(`Deleted: ${data.id}`); break; case 'no_markets': console.log(`No markets: ${data.id}`); break; } }; ws.onerror = (err) => console.error("Error:", err); ws.onclose = () => console.log("Disconnected"); ------------------------------------------------------------------------------ cURL Examples ------------------------------------------------------------------------------ # Get sports (no auth required) curl "https://api.odds-api.io/v3/sports" # Get bookmakers (no auth required) curl "https://api.odds-api.io/v3/bookmakers" # Get football events curl "https://api.odds-api.io/v3/events?apiKey=YOUR_KEY&sport=football" # Get odds for an event curl "https://api.odds-api.io/v3/odds?apiKey=YOUR_KEY&eventId=123456&bookmakers=Bet365,SingBet" # Get value bets curl "https://api.odds-api.io/v3/value-bets?apiKey=YOUR_KEY&bookmaker=Bet365&includeEventDetails=true" # Get arbitrage opportunities curl "https://api.odds-api.io/v3/arbitrage-bets?apiKey=YOUR_KEY&bookmakers=Bet365,SingBet" # Get dropping odds (paid plans only) curl "https://api.odds-api.io/v3/dropping-odds?apiKey=YOUR_KEY&sport=football&minDrop=10&includeEventDetails=true" ================================================================================ BEST PRACTICES ================================================================================ 1. Use Multi-Odds Endpoint - Fetch up to 10 events in one request (counts as 1 API call) - Significantly reduces rate limit usage 2. Cache Responses Appropriately - Event lists: 5-10 minutes - Pre-match odds: 30-60 seconds - Live odds: 5-10 seconds - Use /odds/updated for incremental updates 3. Select Only Needed Bookmakers - Request only bookmakers your users need - Reduces response size and processing time 4. Filter Events by Bookmaker - Use ?bookmaker=Bet365 on /events endpoint to only get events with odds from that bookmaker - Much more efficient than fetching all events and filtering client-side - Example: /events?sport=football&bookmaker=Bet365&apiKey={key} 5. Handle Errors Gracefully - Implement exponential backoff for rate limits (429) - Not all bookmakers offer all markets - Handle resync_required by rebuilding from a REST snapshot with includeSeq=true 6. Monitor Rate Limits - Check response headers for remaining requests - Implement queuing for high-volume applications 7. Use lastSeq for Reconnects - Persist seq from every WebSocket update - Reconnect with lastSeq to recover missed updates without rebuilding state when replay is still available ================================================================================ CONTACT & SUPPORT ================================================================================ Email: hello@odds-api.io Website: https://odds-api.io Documentation: https://docs.odds-api.io Bookmakers List: https://odds-api.io/sportsbooks ================================================================================ END OF DOCUMENTATION ================================================================================ ## Historical Data ### GET /historical/events Past events for a league and date range. Required: apiKey, sport (slug), league (slug), from (RFC3339), to (RFC3339). Response shape matches /events. homeId/awayId cover ~90% of events from March 2026 onward (partial for February 2026, absent earlier); rows without a resolved id omit the fields. Example: https://api.odds-api.io/v3/historical/events?sport=football&league=england-premier-league&from=2026-06-01T00:00:00Z&to=2026-07-01T00:00:00Z&apiKey=KEY ### GET /historical/odds Historical odds for a past event. Required: apiKey, eventId (from /historical/events), bookmakers (comma-separated, max 30). Optional: markets (comma-separated market names; returns all markets when omitted). Response shape matches /odds. The bookmakers parameter accepts any name listed by /v3/bookmakers, and the response reports odds under those same names. ### GET /historical/closing-lines Closing odds for every settled event across up to 10 leagues in one request. Paid plans only (free plans get 403). Required: apiKey, sport (slug), leagues (comma-separated slugs, max 10), from (RFC3339), to (RFC3339, max 366 days after from, so a full season fits in one sweep), markets (comma-separated), bookmakers (comma-separated, max 30). Optional: limit (capped at 100), skip. Returns an array of events, each shaped like the /historical/odds response. Example: https://api.odds-api.io/v3/historical/closing-lines?sport=football&leagues=england-premier-league,spain-la-liga&from=2026-01-01T00:00:00Z&to=2026-01-31T23:59:59Z&markets=ML&bookmakers=Bet365,Unibet&apiKey=KEY ## Billing and Payments Plans are billed monthly in the visitor's local currency (GBP, USD, EUR, BRL, CNY, JPY, KRW). Cards are charged automatically each cycle. ### Paying with crypto Customers can pay with cryptocurrency instead of a card, on any plan. Payments are processed by CoinGate (licensed as a crypto-asset service provider by the Bank of Lithuania). Supported coins include BTC, ETH, USDC, USDT, SOL and LTC, plus 70+ others; prices are quoted in fiat and converted at the rate shown on the payment page. Key difference from cards: crypto cannot be charged automatically, because funds cannot be pulled from a wallet. Each renewal is therefore issued as an invoice with a payment link, sent by email and shown in the dashboard billing page. API access continues while the invoice is open. Reminders follow if it goes unpaid, and an expired payment link can be regenerated from the dashboard. Upgrades, extra request packages and the WebSocket add-on also work on crypto: the prorated amount due is shown before confirming, then paid via a payment link, and the change activates once the payment confirms. Refunds on crypto payments are arranged manually via hello@odds-api.io. Switching between crypto and card billing is handled by support and keeps the same plan and API key. Guide: https://docs.odds-api.io/guides/paying-with-crypto