{"openapi":"3.1.0","info":{"title":"OddsIntel Data API","version":"v1","summary":"Football consensus closing prices, fair prices and price age, for businesses.","description":"Public beta. Derived figures only: no single bookmaker's prices and no bookmaker names. Every figure carries the number of independent sources behind it and the age of its newest input price. History depth by plan: free Sample keys read yesterday, plus today and the next 2 days on a 2-hour delay (meta.delay_minutes), at most 300 rows per answer (meta.truncated); Starter keys read the last 90 days and any upcoming day; Pro and Business keys have no history cap. Other dates answer 403 plan_scope. A plan can only reach as far back as the data we hold: /v1/status gives history_from, the first kick-off date we hold. Responses are cached for up to 60 seconds. We add fields rather than change them where we can. Before any breaking change we email key holders at least 14 days ahead. 18+ · for businesses · not betting advice.","contact":{"name":"OddsIntel Data","email":"data@oddsintel.app","url":"https://data.oddsintel.app/docs"},"termsOfService":"https://data.oddsintel.app/terms"},"servers":[{"url":"https://data.oddsintel.app/v1"}],"security":[{"ApiKey":[]}],"tags":[{"name":"Prices","description":"Fair prices and consensus closing prices per match, market and selection."},{"name":"Grading","description":"CLV grading of prices you took or published, against the consensus close."},{"name":"Lookup","description":"Find our fixture id from your feed's kick-off time and team names."},{"name":"Webhooks","description":"Close-ready notices to your server (Pro and Business)."},{"name":"Status","description":"Data freshness, no key needed."}],"paths":{"/fair":{"get":{"tags":["Prices"],"operationId":"getFair","summary":"Latest fair prices for a date","description":"Latest fair prices for matches kicking off on the given UTC date. History by plan: Sample yesterday (today and the next 2 days 2 h delayed), Starter 90 days, Pro and Business full history.","parameters":[{"$ref":"#/components/parameters/Date"},{"$ref":"#/components/parameters/Market"},{"$ref":"#/components/parameters/Competition"},{"$ref":"#/components/parameters/MaxAge"},{"$ref":"#/components/parameters/AsOf"}],"responses":{"200":{"description":"Rows for the date.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceResponse"},"examples":{"fair":{"summary":"One 1x2 row before kick-off","value":{"data":[{"fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","kickoff":"2026-10-18T14:00:00Z","competition":"Premier League (England)","home":"Arsenal","away":"Chelsea","market":"1x2","line":null,"selection":"home","fair_p":0.512,"fair_odds":1.953,"consensus_close":null,"close_ts":null,"close_is_final":false,"n_sources":4,"price_age_s":84,"status":"provisional","quality_flag":"ok","computed_at":"2026-10-18T13:52:10Z","checked_at":"2026-10-18T13:52:10Z","read_age_s":84,"oldest_input_age_s":695,"dispersion_pp":1.8,"n_sources_band":"4+","close_hash":null,"kickoff_source":"own"},{"fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","kickoff":"2026-10-18T14:00:00Z","competition":"Premier League (England)","home":"Arsenal","away":"Chelsea","market":"ou","line":2.5,"selection":"over","fair_p":0.548,"fair_odds":1.825,"consensus_close":null,"close_ts":null,"close_is_final":false,"n_sources":4,"price_age_s":3912,"status":"provisional","quality_flag":"ok","computed_at":"2026-10-18T13:52:10Z","checked_at":"2026-10-18T13:52:10Z","read_age_s":97,"oldest_input_age_s":708,"dispersion_pp":1.8,"n_sources_band":"4+","close_hash":null,"kickoff_source":"own"},{"fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","kickoff":"2026-10-18T14:00:00Z","competition":"Premier League (England)","home":"Arsenal","away":"Chelsea","market":"btts","line":null,"selection":"yes","fair_p":null,"fair_odds":null,"consensus_close":null,"close_ts":null,"close_is_final":false,"n_sources":2,"price_age_s":null,"status":"withheld","quality_flag":"thin","computed_at":"2026-10-18T13:52:10Z","checked_at":null,"read_age_s":null,"oldest_input_age_s":null,"dispersion_pp":null,"n_sources_band":"2","close_hash":null,"kickoff_source":"own"}],"meta":{"date":"2026-10-18","count":3,"data_as_of":"2026-10-18T13:52:10Z","contract_version":"v1","plan":"starter"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/close":{"get":{"tags":["Prices"],"operationId":"getClose","summary":"Consensus closing prices for a date","description":"Consensus closing prices for matches kicking off on the given UTC date. A close is final (close_is_final: true) once the match has kicked off.","parameters":[{"$ref":"#/components/parameters/Date"},{"$ref":"#/components/parameters/Market"},{"$ref":"#/components/parameters/MaxAge"}],"responses":{"200":{"description":"Rows for the date.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceResponse"},"examples":{"close":{"summary":"One 1x2 row after kick-off","value":{"data":[{"fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","kickoff":"2026-10-18T14:00:00Z","competition":"Premier League (England)","home":"Arsenal","away":"Chelsea","market":"1x2","line":null,"selection":"home","fair_p":0.515,"fair_odds":1.942,"consensus_close":1.942,"close_ts":"2026-10-18T14:00:00Z","close_is_final":true,"n_sources":4,"price_age_s":140,"status":"confirmed_before_ko","quality_flag":"ok","computed_at":"2026-10-18T14:00:30Z","checked_at":"2026-10-18T14:00:30Z","read_age_s":140,"oldest_input_age_s":680,"dispersion_pp":1.8,"n_sources_band":"4+","close_hash":"45aff601d259b3723a1f8410f4695ce06ccf2a38efba6c9ac5499248816f9288","kickoff_source":"own"}],"meta":{"date":"2026-10-18","count":1,"data_as_of":"2026-10-18T14:00:30Z","contract_version":"v1","plan":"starter"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/close.csv":{"get":{"tags":["Prices"],"operationId":"getCloseCsv","summary":"Consensus closing prices for one kick-off day, as a CSV file","description":"The same rows as /close for one UTC kick-off date, as a CSV file: one header row, then one row per match, market and selection. Columns are the FairRow fields in the same order; an empty cell is null; booleans are true / false. A text cell that starts with =, +, - or @ is prefixed with ' so a spreadsheet does not run it. Same key, limits and plan history depth as /close; one file counts as one request. Errors are the same JSON as /close. X-Data-As-Of and X-Contract-Version carry the meta; X-Delay-Minutes is set on a delayed Sample answer.","parameters":[{"$ref":"#/components/parameters/Date"},{"$ref":"#/components/parameters/Market"},{"$ref":"#/components/parameters/MaxAge"}],"responses":{"200":{"description":"The CSV file for the date (attachment; filename oddsintel-close-YYYY-MM-DD.csv).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"Content-Disposition":{"description":"attachment; filename=\"oddsintel-close-YYYY-MM-DD.csv\"","schema":{"type":"string"}},"X-Data-As-Of":{"description":"meta.data_as_of of the same answer (UTC).","schema":{"type":"string","format":"date-time"}},"X-Contract-Version":{"description":"meta.contract_version.","schema":{"type":"string"}},"X-Delay-Minutes":{"description":"Sample keys on today and upcoming days only: meta.delay_minutes.","schema":{"type":"integer"}}},"content":{"text/csv":{"schema":{"type":"string"},"example":"fixture_id,kickoff,competition,home,away,market,line,selection,fair_p,fair_odds,consensus_close,close_ts,close_is_final,n_sources,price_age_s,status,quality_flag,computed_at,checked_at,read_age_s,oldest_input_age_s,dispersion_pp,n_sources_band,close_hash,kickoff_source\n8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47,2026-10-18T14:00:00Z,Premier League (England),Arsenal,Chelsea,1x2,,home,0.515,1.942,1.942,2026-10-18T14:00:00Z,true,4,140,confirmed_before_ko,ok,2026-10-18T14:00:30Z,2026-10-18T14:00:30Z,140,680,1.8,4+,45aff601d259b3723a1f8410f4695ce06ccf2a38efba6c9ac5499248816f9288,own\n"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/clv":{"post":{"tags":["Grading"],"operationId":"postClv","summary":"Grade prices against the consensus close (CLV)","description":"Send a batch of prices you took or published; each comes back with its closing-line value against the same consensus close /v1/close serves: clv = odds × (1 / consensus_close) − 1. Asian handicap (ah) uses the same formula: on a whole or quarter line the close is the break-even price with the push (or half refund) removed, so clv is the value per unit of stake that was not refunded — same sign as the expected value, larger in size on whole and quarter lines; on a half line it is the expected value itself. One call counts as one request, whatever the batch size. A problem with one bet never fails the call: each bet carries its own status. Every graded bet also carries our fair price at taken_at (fair_at_taken_at, edge_vs_fair_at_taken_at_pct). Takeable grading (Pro and Business: was the price seen at our sources at taken_at — takeable seen / near / not_seen / unknown against the best open price among our sources, which is never served; at most 2 different prices checked per selection per account; with takeable_basis, counts and a read age only; no source named) is being finalised: until it is switched on, takeable is \"unchecked\" and takeable_basis and takeable_reason are null. Our sources are the European bookmakers our consensus is built from: a bet at a book we don't collect can show \"not at our sources\". Method: https://data.oddsintel.app/clv-grading Free Sample keys grade up to 100 bets per UTC day (every bet sent counts; then 429 until UTC midnight). Each bet may name the match by fixture_id or by event_id (the Odds API's evt_ id). Pro and Business can upload up to 2,000 bets at once: POST /v1/clv/bulk.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClvRequest"},"examples":{"clv":{"summary":"Three tips on one match","value":{"bets":[{"ref":"tip-101","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","market":"1x2","selection":"home","odds":2.1,"taken_at":"2026-10-18T09:30:00Z"},{"ref":"tip-102","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","market":"1x2","selection":"draw","odds":3.6,"taken_at":"2026-10-18T09:30:00Z"},{"ref":"tip-103","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","market":"ou","line":2.5,"selection":"over","odds":1.95,"taken_at":"2026-10-18T14:05:00Z"}]}}}}}},"responses":{"200":{"description":"One row per bet, in the order sent.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClvResponse"},"examples":{"clv":{"summary":"One graded, one without a consensus, one taken after kick-off","value":{"data":[{"ref":"tip-101","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","market":"1x2","line":null,"selection":"home","odds":2.1,"taken_at":"2026-10-18T09:30:00Z","status":"graded","clv":0.0814,"consensus_close":1.942,"close_fair_p":0.5149,"n_sources":4,"close_ts":"2026-10-18T14:00:00Z","kickoff":"2026-10-18T14:00:00Z","message":null,"takeable":"unchecked","takeable_basis":null,"fair_at_taken_at":2.05,"edge_vs_fair_at_taken_at_pct":2.44,"takeable_reason":null,"event_id":"evt_01M5EB1T6CXE9EC1DNKW54Q9A7"},{"ref":"tip-102","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","market":"1x2","line":null,"selection":"draw","odds":3.6,"taken_at":"2026-10-18T09:30:00Z","status":"no_consensus","clv":null,"consensus_close":null,"close_fair_p":null,"n_sources":2,"close_ts":"2026-10-18T14:00:00Z","kickoff":"2026-10-18T14:00:00Z","message":"Fewer than 3 independent sources at the close: no figure, never a guess.","takeable":null,"takeable_basis":null,"fair_at_taken_at":null,"edge_vs_fair_at_taken_at_pct":null,"takeable_reason":null,"event_id":"evt_01M5EB1T6CXE9EC1DNKW54Q9A7"},{"ref":"tip-103","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","market":"ou","line":2.5,"selection":"over","odds":1.95,"taken_at":"2026-10-18T14:05:00Z","status":"taken_after_kickoff","clv":null,"consensus_close":null,"close_fair_p":null,"n_sources":null,"close_ts":null,"kickoff":"2026-10-18T14:00:00Z","message":"A price taken less than 60 seconds before kick-off, or later, is not graded against the close.","takeable":null,"takeable_basis":null,"fair_at_taken_at":null,"edge_vs_fair_at_taken_at_pct":null,"takeable_reason":null,"event_id":"evt_01M5EB1T6CXE9EC1DNKW54Q9A7"}],"meta":{"count":3,"graded":1,"data_as_of":"2026-10-19T08:00:30Z","contract_version":"v1","plan":"starter","max_bets":50,"day_bets_limit":null,"day_bets_remaining":null}}}}}}},"400":{"description":"The body is not {\"bets\": [...]}, is empty, or has more bets than your plan allows (bad_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"bad_request","message":"Your plan grades at most 50 bets per call; split the batch."}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/clv/bulk":{"post":{"tags":["Grading"],"operationId":"postClvBulk","summary":"Grade a file of bets against the consensus close (Pro and Business)","description":"The same grader as POST /v1/clv, for a whole record at once: up to 2,000 bets per call. Send a CSV file (Content-Type: text/csv) with a header row — fixture_id (or event_id), market, line, selection, odds, taken_at, and optionally ref; columns in any order, others ignored — or the /v1/clv JSON body. Rows come back in the order sent, each with its own status, exactly as /v1/clv grades them. Add ?format=csv for a CSV file back (the /v1/clv row fields except the nested takeable_basis). One call counts as one request; the bets graded are metered as rows. Pro and Business only (403 plan_scope otherwise). Method: https://data.oddsintel.app/clv-grading","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClvRequest"},"examples":{"clv":{"summary":"Three tips on one match","value":{"bets":[{"ref":"tip-101","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","market":"1x2","selection":"home","odds":2.1,"taken_at":"2026-10-18T09:30:00Z"},{"ref":"tip-102","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","market":"1x2","selection":"draw","odds":3.6,"taken_at":"2026-10-18T09:30:00Z"},{"ref":"tip-103","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","market":"ou","line":2.5,"selection":"over","odds":1.95,"taken_at":"2026-10-18T14:05:00Z"}]}}}},"text/csv":{"schema":{"type":"string"},"example":"ref,fixture_id,market,line,selection,odds,taken_at\ntip-101,8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47,1x2,,home,2.1,2026-10-18T09:30:00Z\ntip-103,8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47,ou,2.5,over,1.95,2026-10-18T14:05:00Z\n"}}},"responses":{"200":{"description":"One row per bet, in the order sent.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClvResponse"},"examples":{"clv":{"summary":"One graded, one without a consensus, one taken after kick-off","value":{"data":[{"ref":"tip-101","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","market":"1x2","line":null,"selection":"home","odds":2.1,"taken_at":"2026-10-18T09:30:00Z","status":"graded","clv":0.0814,"consensus_close":1.942,"close_fair_p":0.5149,"n_sources":4,"close_ts":"2026-10-18T14:00:00Z","kickoff":"2026-10-18T14:00:00Z","message":null,"takeable":"unchecked","takeable_basis":null,"fair_at_taken_at":2.05,"edge_vs_fair_at_taken_at_pct":2.44,"takeable_reason":null,"event_id":"evt_01M5EB1T6CXE9EC1DNKW54Q9A7"},{"ref":"tip-102","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","market":"1x2","line":null,"selection":"draw","odds":3.6,"taken_at":"2026-10-18T09:30:00Z","status":"no_consensus","clv":null,"consensus_close":null,"close_fair_p":null,"n_sources":2,"close_ts":"2026-10-18T14:00:00Z","kickoff":"2026-10-18T14:00:00Z","message":"Fewer than 3 independent sources at the close: no figure, never a guess.","takeable":null,"takeable_basis":null,"fair_at_taken_at":null,"edge_vs_fair_at_taken_at_pct":null,"takeable_reason":null,"event_id":"evt_01M5EB1T6CXE9EC1DNKW54Q9A7"},{"ref":"tip-103","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","market":"ou","line":2.5,"selection":"over","odds":1.95,"taken_at":"2026-10-18T14:05:00Z","status":"taken_after_kickoff","clv":null,"consensus_close":null,"close_fair_p":null,"n_sources":null,"close_ts":null,"kickoff":"2026-10-18T14:00:00Z","message":"A price taken less than 60 seconds before kick-off, or later, is not graded against the close.","takeable":null,"takeable_basis":null,"fair_at_taken_at":null,"edge_vs_fair_at_taken_at_pct":null,"takeable_reason":null,"event_id":"evt_01M5EB1T6CXE9EC1DNKW54Q9A7"}],"meta":{"count":3,"graded":1,"data_as_of":"2026-10-19T08:00:30Z","contract_version":"v1","plan":"pro","max_bets":2000,"day_bets_limit":null,"day_bets_remaining":null}}}}},"text/csv":{"schema":{"type":"string"},"description":"With ?format=csv: a header row, then one row per bet."}}},"400":{"description":"The body is not {\"bets\": [...]}, is empty, or has more bets than your plan allows (bad_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"bad_request","message":"Your plan grades at most 50 bets per call; split the batch."}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}},"parameters":[{"name":"format","in":"query","required":false,"description":"json (default) or csv: the answer's format.","schema":{"type":"string","enum":["json","csv"],"default":"json"}}]}},"/fixtures":{"get":{"tags":["Lookup"],"operationId":"getFixtures","summary":"Find our fixture id from a kick-off time and team names, or from an id you already hold","description":"Join our figures to the odds feed you already use. Send the kick-off time and the two team names as your feed spells them; get back up to 5 of our fixtures kicking off within 15 minutes either side, best first, each with a confidence from 0 to 1. Confidence is the mean of the two team-name similarities (accents, punctuation and words such as FC, AC or SK are ignored; a shortened name scores 0.9 against the full one; a women's, U21 or reserve team never scores above 0.5 against the first team), times 1.0 at the same minute down to 0.8 at 15 minutes apart. Home and away are tried both ways: teams_reversed true means your home team is our away team, so swap home and away before you compare 1X2 prices. Join rule: 0.9 or more, join; 0.6 to 0.9, check by eye; below 0.6, treat as not found. The kick-off day must be inside your plan's history window. One call counts as one request. Or send ONE id instead of kickoff / home / away — fixture_id (ours) or event_id (the Odds API's evt_ id) — and get the fixtures that carry it (confidence 1.0; an exact id, no name matching; empty when we hold no such id or the match is outside your plan's window). Every row carries event_id (null when we hold none).","parameters":[{"name":"kickoff","in":"query","required":false,"description":"Kick-off time, ISO 8601 with a timezone (Z or +hh:mm). Required unless you send one id parameter.","schema":{"type":"string","format":"date-time"},"example":"2026-10-20T19:00:00Z"},{"name":"home","in":"query","required":false,"description":"Home team as your feed names it (1 to 80 characters). Required unless you send one id parameter.","schema":{"type":"string","minLength":1,"maxLength":80},"example":"Man Utd"},{"name":"away","in":"query","required":false,"description":"Away team as your feed names it (1 to 80 characters). Required unless you send one id parameter.","schema":{"type":"string","minLength":1,"maxLength":80},"example":"Chelsea"},{"name":"fixture_id","in":"query","required":false,"description":"Look up by our fixture id.","schema":{"type":"string","format":"uuid"}},{"name":"event_id","in":"query","required":false,"description":"Look up by the Odds API's event id (evt_ + 26 characters).","schema":{"type":"string"},"example":"evt_01M5EB1T6CXE9EC1DNKW54Q9A7"}],"responses":{"200":{"description":"Matching fixtures, best first (empty when none scores 0.4 or more).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FixturesResponse"},"examples":{"fixtures":{"summary":"One clear match","value":{"data":[{"fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","kickoff":"2026-10-20T19:00:00Z","competition":"Premier League (England)","home":"Manchester United","away":"Chelsea","confidence":1,"kickoff_delta_s":0,"teams_reversed":false,"kickoff_source":"own","event_id":"evt_01M5EB1T6CXE9EC1DNKW54Q9A7"}],"meta":{"count":1,"kickoff":"2026-10-20T19:00:00Z","window_s":900,"data_as_of":"2026-10-20T08:14:00Z","contract_version":"v1","plan":"starter","lookup":"teams"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/status":{"get":{"tags":["Status"],"operationId":"getStatus","summary":"Data freshness","description":"No key needed. status is \"stale\" when the newest data is older than max_age_s; the data endpoints then answer 503 data_stale.","security":[],"responses":{"200":{"description":"Current data freshness.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"},"examples":{"ok":{"value":{"status":"ok","data_as_of":"2026-10-18T13:52:10Z","max_age_s":600,"n_matches":752,"history_from":"2026-10-17"}}}}}}}}},"/close/bulk":{"get":{"tags":["Prices"],"operationId":"getCloseBulk","summary":"Consensus closing prices for up to 31 kick-off days, as one gzip CSV file","description":"Starter, Pro and Business. The /close.csv rows for every kick-off day (UTC) from from to to, at most 31 days, as one gzip-compressed CSV file: one header row (the FairRow fields, in order), then each day in date order. Same cells as /close.csv. Your plan's history depth applies to from. One file counts as one request; the rows in it are counted as rows served. The file is built day by day: an error part-way cuts it, and gzip then reports it as damaged — download it again. The free Sample plan answers 403 plan_scope (use /close.csv). At most two files are built at a time; otherwise 503 busy with Retry-After.","parameters":[{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"},{"$ref":"#/components/parameters/Market"}],"responses":{"200":{"description":"The file (attachment; filename oddsintel-close-FROM_TO.csv.gz).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"Content-Disposition":{"description":"attachment; filename=\"oddsintel-close-YYYY-MM-DD_YYYY-MM-DD.csv.gz\"","schema":{"type":"string"}},"X-Data-As-Of":{"description":"data_as_of when the file was started (UTC).","schema":{"type":"string","format":"date-time"}},"X-Contract-Version":{"description":"contract_version.","schema":{"type":"string"}}},"content":{"application/gzip":{"schema":{"type":"string","contentEncoding":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/close/changelog":{"get":{"tags":["Prices"],"operationId":"getCloseChangelog","summary":"Every published close that changed after it was first published","description":"Closes do not change once published. This list proves it: every published final close is recorded with its close_hash when we first serve it and checked again every 2 minutes (older days once a day). Any close whose served hash later differs is listed here with its old and new hash, the reason and the time. An empty list since meta.ledger_from means no published close has changed. No figures, so every plan can read it; fetch the new figure from /close. One call counts as one request; at most 1000 rows per answer, oldest first.","parameters":[{"name":"since","in":"query","required":false,"description":"Changes at or after this time, ISO 8601 with a timezone. Default: 7 days ago.","schema":{"type":"string","format":"date-time"}},{"name":"after","in":"query","required":false,"description":"Only ids above this (meta.next_after of the previous page).","schema":{"type":"integer","minimum":0}},{"name":"fixture_id","in":"query","required":false,"description":"One fixture only.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangelogResponse"},"examples":{"changelog":{"summary":"One close re-taken after a kick-off move","value":{"data":[{"id":17,"fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","kickoff":"2026-10-18T16:30:00Z","market":"1x2","line":null,"selection":"home","old_hash":"0f8b6c0a6c2b5f0a3e9d7c1b2a4f6e8d0c2b4a6f8e0d2c4b6a8f0e2d4c6b8a0f","new_hash":"f0a8b6c4d2e0f8a6b4c2d0e8f6a4b2c0d8e6f4a2b1c7d9e3a0f5b2c6a0c6b8f0","reason":"kickoff_moved","first_published_at":"2026-10-18T14:17:02Z","changed_at":"2026-10-18T16:47:12Z"}],"meta":{"count":1,"since":"2026-10-13T00:00:00Z","next_after":null,"ledger_from":"2026-10-20T18:00:00Z","ledger_checked_at":"2026-10-20T18:02:00Z","contract_version":"v1","plan":"starter"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/webhook":{"get":{"tags":["Webhooks"],"operationId":"getWebhook","summary":"Your webhook and its last deliveries","description":"Pro and Business. data is null when none is registered. The secret is never shown here. One call counts as one request.","responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"},"examples":{"webhook":{"summary":"A registered webhook","value":{"data":{"url":"https://example.com/hooks/oddsintel","status":"active","events":["close.ready","close.changed"],"consecutive_failures":0,"disabled_at":null,"disabled_reason":null,"created_at":"2026-10-20T18:00:00Z","updated_at":"2026-10-20T18:00:00Z","recent_deliveries":[{"id":4021,"event_id":"6b1f0f8e-2b9c-4d5e-8a3f-1c2d3e4f5a6b","type":"close.ready","fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","status":"delivered","attempts":1,"last_attempt_at":"2026-10-20T19:17:05Z","last_http_status":200,"last_error":null,"delivered_at":"2026-10-20T19:17:05Z","created_at":"2026-10-20T19:17:02Z"}]},"meta":{"contract_version":"v1","plan":"pro"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}},"put":{"tags":["Webhooks"],"operationId":"putWebhook","summary":"Register or replace your webhook URL","description":"Pro and Business: one https URL per key. We POST a small JSON notice (WebhookEvent) when a fixture's closes become final (close.ready) and when a published close changes (close.changed). The answer carries data.secret ONCE: store it and check every notice's OddsIntel-Signature header with it. PUT again to change the URL, get a new secret (the old one stops at once) or switch a disabled webhook back on. 400 bad_request when the URL is not https, uses an IP address or resolves to a private address; 503 not_open_yet while webhooks are not switched on. One call counts as one request.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRequest"},"example":{"url":"https://example.com/hooks/oddsintel"}}}},"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}},"delete":{"tags":["Webhooks"],"operationId":"deleteWebhook","summary":"Remove your webhook","description":"Pro and Business. Stops every notice; its delivery log goes too. data.deleted is false when none was registered.","responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"object","properties":{"deleted":{"type":"boolean"}}},"meta":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/webhook/test":{"post":{"tags":["Webhooks"],"operationId":"testWebhook","summary":"Send a test notice (ping) to your webhook","description":"Pro and Business. Queues one ping notice, signed like every other, usually delivered within 15 seconds; see it in GET /webhook recent_deliveries. 400 when no webhook is registered.","responses":{"200":{"description":"Queued.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"object","properties":{"queued":{"type":"boolean"},"event_id":{"type":"string","format":"uuid"}}},"meta":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}}},"components":{"securitySchemes":{"ApiKey":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"Your key, starting oid_. Free Sample keys are e-mailed at once; paid plans are reviewed within 24 hours. Get one at https://data.oddsintel.app/request-key."}},"parameters":{"Date":{"name":"date","in":"query","required":true,"description":"Kick-off date (UTC), YYYY-MM-DD.","schema":{"type":"string","format":"date"},"example":"2026-10-18"},"Market":{"name":"market","in":"query","required":false,"description":"Only this market. Omit for all markets.","schema":{"type":"string","enum":["1x2","ou","btts","ah"]}},"Competition":{"name":"competition","in":"query","required":false,"description":"Only matches whose competition contains this text (case-insensitive, at most 80 characters), e.g. premier league or england. /v1/fair only; /v1/close ignores it.","schema":{"type":"string"}},"MaxAge":{"name":"max_age","in":"query","required":false,"description":"Leave out provisional (before kick-off) rows whose oldest input is older than this many seconds (read age where the oldest is not recorded). Closes and withheld rows are never left out. 1 to 86400.","schema":{"type":"integer","minimum":1,"maximum":86400},"example":900},"AsOf":{"name":"as_of","in":"query","required":false,"description":"The rows as they stood at this time (ISO 8601 with a timezone), from our history: for backtesting a comparison at a fixed time before kick-off. Floored to the 5-minute publish grid; ages are measured at that time. Not in the future. On a free Sample key never later than its delay. /v1/fair only.","schema":{"type":"string","format":"date-time"},"example":"2026-10-18T13:00:00Z"},"From":{"name":"from","in":"query","required":true,"description":"First kick-off date (UTC), YYYY-MM-DD.","schema":{"type":"string","format":"date"},"example":"2026-10-01"},"To":{"name":"to","in":"query","required":true,"description":"Last kick-off date (UTC), YYYY-MM-DD, on or after from; at most 31 days in one file.","schema":{"type":"string","format":"date"},"example":"2026-10-31"}},"headers":{"X-RateLimit-Limit":{"description":"Your plan's requests per minute.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying (on 429, and on 503 busy).","schema":{"type":"integer"}}},"responses":{"BadRequest":{"description":"A parameter is missing or malformed (bad_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"bad_request","message":"date must be YYYY-MM-DD"}}}},"Unauthorized":{"description":"The X-Api-Key header is missing (missing_key) or the key is not valid (invalid_key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"missing_key","message":"Send your key in the X-Api-Key header."}}}},"Forbidden":{"description":"The key has been revoked (revoked), or the date is outside the key's plan (plan_scope: Sample covers yesterday to 2 days ahead, Starter the last 90 days and any upcoming day; Pro and Business have no history cap).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"revoked":{"value":{"error":"revoked","message":"This key has been revoked."}},"plan_scope":{"value":{"error":"plan_scope","message":"The free Sample plan covers 2026-10-11 to 2026-10-20; today and upcoming days are 120 minutes delayed. Starter and above have no delay and longer history."}},"plan_scope_starter":{"value":{"error":"plan_scope","message":"The Starter plan covers kick-off days from 2026-07-20 (90 days back). Pro and Business have no history cap."}}}}}},"RateLimited":{"description":"Your plan's limit is reached (rate_limited). Wait Retry-After seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"rate_limited","message":"Rate limit reached. Retry after 30 seconds."}}}},"Unavailable":{"description":"The data is older than 10 minutes (data_stale): we refuse rather than serve stale data. Or the beta is at its request limit (busy, Retry-After in seconds), or the API is not open yet (not_open_yet).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"data_stale","message":"Data is older than 10 minutes. Check /v1/status and retry."}}},"headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"}}}},"schemas":{"FairRow":{"type":"object","description":"One selection of one market of one match. When fewer than 3 independent sources are available the row has status \"withheld\" and its figures are null.","required":["fixture_id","kickoff","competition","home","away","market","line","selection","fair_p","fair_odds","consensus_close","close_ts","close_is_final","n_sources","price_age_s","status","quality_flag","computed_at","checked_at","read_age_s","oldest_input_age_s","dispersion_pp","n_sources_band","close_hash","kickoff_source"],"properties":{"fixture_id":{"type":"string","format":"uuid","description":"Stable match id."},"kickoff":{"type":"string","format":"date-time","description":"Kick-off time, UTC."},"competition":{"type":"string","description":"Competition and country.","examples":["Premier League (England)"]},"home":{"type":"string"},"away":{"type":"string"},"market":{"type":"string","enum":["1x2","ou","btts","ah"]},"line":{"type":["number","null"],"description":"Goal line for ou (e.g. 2.5); for ah the HOME side's handicap on both selections, quarter lines as published (line -0.25 with selection away = the away side at +0.25); null for 1x2 and btts."},"selection":{"type":"string","enum":["home","draw","away","over","under","yes","no"],"description":"1x2: home / draw / away. ou: over / under. btts: yes / no. ah: home / away."},"fair_p":{"type":["number","null"],"minimum":0,"maximum":1,"description":"Consensus probability with the margin removed (Shin method for 1x2, power method for ou, btts and ah). 3 decimal places. On a whole or quarter ah / ou line it is the break-even probability (the push or half-refund removed), so 1 / fair_p is the fair price but not the chance of winning the bet outright."},"fair_odds":{"type":["number","null"],"description":"1 / fair_p. Before kick-off it is computed from fair_p as served (3 dp), so it carries no finer figure than fair_p."},"consensus_close":{"type":["number","null"],"description":"The fair price (1 / fair_p) as it stood at kick-off, in decimal odds; null before kick-off."},"close_ts":{"type":["string","null"],"format":"date-time","description":"When the close was taken; null before."},"close_is_final":{"type":"boolean","description":"true once the match has kicked off."},"n_sources":{"type":"integer","minimum":0,"description":"Independent sources behind the figure, capped at 4: 4 means 4 or more (n_sources_band says it in words). Brands sharing one trading desk count once.","maximum":4},"price_age_s":{"type":["integer","null"],"minimum":0,"description":"Value age: seconds since the figure last changed, measured from its newest input price at that change. Before kick-off it keeps growing while the market does not move, even though we keep checking it; use read_age_s for how fresh the reads behind it are. On a close (close_is_final true): the newest input's age at kick-off."},"status":{"type":"string","enum":["provisional","confirmed_before_ko","withheld"]},"quality_flag":{"type":"string","enum":["ok","stale","thin"],"description":"ok on every published row. On a withheld row, why: stale (at least 3 independent sources quote the market, but too few prices were read in the last 45 minutes, or we have not re-checked the figure in the last 10 minutes) or thin (fewer than 3 independent sources)."},"computed_at":{"type":"string","format":"date-time","description":"When this row was computed, UTC."},"checked_at":{"type":["string","null"],"format":"date-time","description":"The last time we re-computed this figure from our latest reads, UTC. The figure itself is rewritten only when it moves, so checked_at can be newer than computed_at. On a close: when the close was computed. Null when withheld."},"read_age_s":{"type":["integer","null"],"minimum":0,"description":"Read age: seconds since the newest input price behind the figure was last confirmed by a read, as of checked_at. Before kick-off every input had been read within the last 45 minutes at checked_at (the freshness rule); if we stop re-checking a match, it grows. On a close: equals price_age_s. Never younger than the truth. Null when withheld."},"oldest_input_age_s":{"type":["integer","null"],"minimum":0,"description":"Age of the OLDEST input price behind the figure, in seconds (read_age_s is the newest's). Before kick-off: now − the oldest input's last confirmed read as of checked_at; it can be up to 45 minutes larger than read_age_s, and with 3 independent sources one lagging input moves the figure by a third of its lag. On a close: the oldest input's age at kick-off. Null when withheld or not recorded (rows before 19 October 2026)."},"dispersion_pp":{"type":["number","null"],"minimum":0,"description":"How much the independent sources disagree on this selection: highest minus lowest margin-free probability among them, in percentage points, 1 dp. No source is named. A gap between your price and fair_odds smaller than this is noise. Null when withheld or not recorded (rows before 19 October 2026)."},"n_sources_band":{"type":"string","enum":["0","1","2","3","4+"],"description":"n_sources as a band: \"3\" or \"4+\" on a priced row (fewer than 3 is never priced)."},"close_hash":{"type":["string","null"],"pattern":"^[0-9a-f]{64}$","description":"A published final close only (close_is_final true and consensus_close set): sha256, in hex, of the line oddsintel-close-v1|fixture_id|kickoff|market|line|selection|consensus_close|close_ts with kickoff and close_ts as served (YYYY-MM-DDTHH:MM:SSZ), line with 2 decimals (empty for 1x2 and btts) and consensus_close with 3 decimals. Recompute it from the row to check a close you stored. Closes do not change after they are published; if one ever does, GET /close/changelog lists it with its old and new hash. Null on every other row."},"kickoff_source":{"type":"string","enum":["own","fallback"],"description":"Where kickoff came from (D2.4, appended): 'own' = the kick-off at least two independent bookmakers agree on (within 5 minutes, a majority of those still listing the match); 'fallback' = our cross-reference schedule, used when fewer than two agree or they disagree with it by more than 10 minutes (then the close is withheld)."},"event_id":{"type":["string","null"],"pattern":"^evt_[0-9A-HJKMNP-TV-Z]{26}$","description":"The same match's event id in the OddsIntel Odds API (evt_ + 26 characters). Null when we hold none. Optional on this row, being switched on: until then it is absent; /v1/fixtures always carries it."}}},"Meta":{"type":"object","required":["date","count","data_as_of","contract_version","plan"],"properties":{"date":{"type":"string","format":"date"},"count":{"type":"integer","minimum":0,"description":"Number of rows in data."},"data_as_of":{"type":"string","format":"date-time","description":"Time of the newest data behind this response, UTC."},"contract_version":{"type":"string","const":"v1"},"plan":{"type":"string","enum":["sample","starter","pro","business","trial"],"description":"The plan of the key used."},"delay_minutes":{"type":"integer","description":"Present only on free Sample keys for today and upcoming days: the figures are as they were this many minutes ago (default 120).","example":120},"truncated":{"type":"boolean","description":"Present and true when a free Sample answer was cut at 300 rows. Paid plans are never cut."},"as_of":{"type":"string","format":"date-time","description":"Present only when as_of was asked: the time the rows are shown as of (as_of floored to the 5-minute grid). Also present on a delayed free Sample answer (with delay_minutes): the time the delayed rows are as of."},"max_age":{"type":"integer","description":"Present only when max_age was asked: the value applied."}}},"PriceResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/FairRow"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"Status":{"type":"object","required":["status","data_as_of","max_age_s","history_from"],"properties":{"status":{"type":"string","enum":["ok","stale"]},"data_as_of":{"type":"string","format":"date-time"},"max_age_s":{"type":"integer","description":"Data older than this is stale.","examples":[600]},"n_matches":{"type":["integer","null"],"description":"Matches with a published figure, kick-off today or in the next 2 UTC days. Null if the count is unavailable.","examples":[752]},"history_from":{"type":["string","null"],"format":"date","description":"The first kick-off date (UTC) we hold figures for. No plan reaches further back than this, whatever its history depth. Null if unavailable.","examples":["2026-10-17"]},"stale_reason":{"type":["string","null"],"enum":["engine","inputs",null],"description":"Which clock says stale: engine = the price engine has not completed a run for 10 minutes (the pipeline stopped; /v1/fair and /v1/close answer 503 data_stale); inputs = the engine runs but nothing upcoming is priced, or the newest input read is more than 30 minutes old. null when ok."},"engine":{"type":["object","null"],"description":"The price engine's heartbeat.","properties":{"last_ok_run_at":{"type":["string","null"],"format":"date-time"},"last_run_ok":{"type":["boolean","null"]},"newest_figure_at":{"type":["string","null"],"format":"date-time"}}},"inputs":{"type":["object","null"],"description":"Health of the inputs behind the figures (null when not available).","properties":{"status":{"type":"string","enum":["ok","dead"]},"newest_input_age_s":{"type":["integer","null"]},"close_lag_s":{"type":"integer"},"markets":{"type":"object","additionalProperties":{"type":"object","properties":{"priced_groups":{"type":"integer"},"priced_groups_7d_ago":{"type":["integer","null"]}}}}}}}},"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["bad_request","missing_key","invalid_key","revoked","rate_limited","data_stale","not_open_yet","busy","plan_scope","wrong_product"]},"message":{"type":"string","description":"Human-readable explanation."}}},"ClvBet":{"type":"object","description":"One price to grade. Unknown properties are ignored.","required":["market","selection","odds","taken_at"],"properties":{"ref":{"type":"string","maxLength":64,"description":"Your own id for this bet, echoed back."},"fixture_id":{"type":"string","format":"uuid","description":"The match id from /v1/fair or /v1/close. Send this or event_id."},"market":{"type":"string","enum":["1x2","ou","btts","ah"]},"line":{"type":["number","null"],"description":"Goal line, required for ou (e.g. 2.5). Required for ah: the HOME side's handicap in quarter steps, for both selections, as /v1/close serves it (a bet on the away side at +0.25 is line -0.25, selection away). Omit or null for 1x2 and btts."},"selection":{"type":"string","enum":["home","draw","away","over","under","yes","no"],"description":"1x2: home / draw / away. ou: over / under. btts: yes / no. ah: home / away."},"odds":{"type":"number","minimum":1.01,"maximum":1000,"description":"The decimal price you took or published. Graded rounded to 3 decimals."},"taken_at":{"type":"string","format":"date-time","description":"When you took or published the price (ISO 8601 with Z or an offset). Required: a bet without it is not graded (invalid). A price taken less than 60 seconds before kick-off, or later, is not graded (taken_after_kickoff)."},"event_id":{"type":"string","pattern":"^evt_[0-9A-HJKMNP-TV-Z]{26}$","description":"Instead of fixture_id: the match's event id in the OddsIntel Odds API (evt_ + 26 characters). An id we cannot place grades as unknown_fixture."}},"anyOf":[{"required":["fixture_id"]},{"required":["event_id"]}]},"ClvRequest":{"type":"object","required":["bets"],"properties":{"bets":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/ClvBet"},"description":"Up to 10 bets per call on Sample, 50 on Starter, 500 on Pro and Business. One call counts as one request against your limits."}}},"ClvRow":{"type":"object","description":"One graded bet, in the order you sent it. Figures are null unless status is graded.","required":["ref","fixture_id","market","line","selection","odds","taken_at","status","clv","consensus_close","close_fair_p","n_sources","close_ts","kickoff","message","takeable","takeable_basis","fair_at_taken_at","edge_vs_fair_at_taken_at_pct","takeable_reason","event_id"],"properties":{"ref":{"type":["string","null"],"description":"Your ref, echoed."},"fixture_id":{"type":["string","null"],"format":"uuid"},"market":{"type":["string","null"],"enum":["1x2","ou","btts","ah",null]},"line":{"type":["number","null"]},"selection":{"type":["string","null"]},"odds":{"type":["number","null"],"description":"Your price, as graded."},"taken_at":{"type":["string","null"],"format":"date-time"},"status":{"type":"string","enum":["graded","not_closed_yet","no_consensus","unknown_fixture","unknown_selection","taken_after_kickoff","plan_scope","invalid","implausible_price"],"description":"graded · not_closed_yet (before kick-off) · no_consensus (under 3 independent sources at the close: no figure, never a guess) · unknown_fixture · unknown_selection (no figure for that market, line and selection) · taken_after_kickoff · plan_scope (outside your plan's window) · invalid (see message: e.g. taken_at missing) · implausible_price (more than 25 % above the margin-free closing price: no real offer sits there, not graded)."},"clv":{"type":["number","null"],"description":"Closing-line value as a fraction: odds × close_fair_p − 1. 0.05 = 5 % better than the close."},"consensus_close":{"type":["number","null"],"description":"The consensus closing price (decimal odds), the same figure /v1/close serves."},"close_fair_p":{"type":["number","null"],"minimum":0,"maximum":1,"description":"1 / consensus_close."},"n_sources":{"type":["integer","null"],"minimum":0,"description":"Independent sources behind the close."},"close_ts":{"type":["string","null"],"format":"date-time"},"kickoff":{"type":["string","null"],"format":"date-time"},"message":{"type":["string","null"],"description":"Why a bet was not graded; null when graded."},"takeable":{"type":["string","null"],"enum":["unchecked","seen","near","not_seen","unknown",null],"description":"Was the price seen at our sources at taken_at? BEING FINALISED: until takeable grading is switched on, every graded bet says \"unchecked\". Once on (Pro and Business; other plans: unchecked with takeable_reason plan), a band against the best open price among our sources at taken_at, which is never served: seen = seen at our sources (at least one of them had an open price at or above yours); near = near our sources' best price (above it by at most 4 %); not_seen = not at our sources (more than 4 % above the best, or none had the selection open); unknown = fewer than 3 of our sources have a known state and the price was not seen (thin coverage), or we know the state of none of our sources at taken_at (a source counts when taken_at falls inside our continuous reads of it, its last read was at most 900 seconds earlier, or taken_at lies between two of our reads of it at most twice its polling interval apart with no price change between them), or the check limit was reached. Our sources are the European bookmakers our consensus is built from: a bet at a book we don't collect can show \"not at our sources\". Indicative, only about the sources we observe; it says the price was seen, not that you took it. Null unless status is graded."},"takeable_basis":{"type":["object","null"],"required":["n_sources","max_read_age_s"],"properties":{"n_sources":{"type":"integer","minimum":0,"maximum":4,"description":"Independent sources whose state at taken_at is known; 4 means 4 or more."},"max_read_age_s":{"type":["integer","null"],"minimum":0,"description":"Seconds between taken_at and the oldest of those sources' last reads (0 = inside continuous reads); null when no source is known."}},"description":"What the takeable answer rests on: counts and a read age only. Null while takeable grading is being finalised (takeable \"unchecked\"), on plans without it, when the check limit was reached, unless status is graded, and when the evidence could not be read."},"fair_at_taken_at":{"type":["number","null"],"description":"Our fair price for this selection as /v1/fair?as_of=<taken_at> serves it (the figure in force at taken_at, on the 5-minute publish grid), served only when that figure was standing and being re-checked then. Null when we had no figure then, it was withheld, or we cannot show it was being checked; null unless status is graded."},"edge_vs_fair_at_taken_at_pct":{"type":["number","null"],"description":"(odds / fair_at_taken_at − 1) × 100, 2 decimal places: your price against our fair price at the time you took it. A measurement, not a prediction."},"takeable_reason":{"type":["string","null"],"enum":["plan","probe_limit","too_few_sources","thin_coverage","unavailable","no_account","market",null],"description":"Why takeable is unknown or unchecked: plan (takeable grading is on Pro and Business), probe_limit (this account already had 2 different prices checked for this fixture, market, line and selection; sending one of those again is free), too_few_sources (no source with a known state), thin_coverage (fewer than 3 of our sources known and the price not seen: we make no negative claim from thin coverage), unavailable (the evidence could not be read), no_account, market (takeable grading covers 1x2, ou and btts; an ah bet is not checked). Null otherwise."},"event_id":{"type":["string","null"],"description":"The match's Odds API event id (evt_), when we hold it. Appended 2026-10-23."}}},"ClvMeta":{"type":"object","required":["count","graded","data_as_of","contract_version","plan","max_bets","day_bets_limit","day_bets_remaining"],"properties":{"count":{"type":"integer"},"graded":{"type":"integer","description":"Bets with status graded."},"data_as_of":{"type":"string","format":"date-time"},"contract_version":{"type":"string","enum":["v1"]},"plan":{"type":"string","enum":["sample","starter","pro","business","trial"]},"max_bets":{"type":"integer","description":"Your plan's batch size (per call; POST /v1/clv/bulk: per file)."},"day_bets_limit":{"type":["integer","null"],"description":"Free Sample keys: bets graded per UTC day (100; every bet sent counts). Null on paid plans."},"day_bets_remaining":{"type":["integer","null"],"description":"Free Sample keys: bets left today after this call. Null on paid plans."}}},"ClvResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ClvRow"}},"meta":{"$ref":"#/components/schemas/ClvMeta"}}},"Fixture":{"type":"object","required":["fixture_id","kickoff","competition","home","away","confidence","kickoff_delta_s","teams_reversed","kickoff_source","event_id"],"properties":{"fixture_id":{"type":"string","format":"uuid","description":"Our fixture id: the fixture_id of /v1/fair, /v1/close and /v1/clv."},"kickoff":{"type":"string","format":"date-time","description":"Our kick-off time (UTC)."},"competition":{"type":"string","description":"Competition (country)."},"home":{"type":["string","null"],"description":"Our home team name."},"away":{"type":["string","null"],"description":"Our away team name."},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"How sure the match is, 0 to 1. 0.9 or more: join; 0.6 to 0.9: check; below: not found."},"kickoff_delta_s":{"type":"integer","description":"Our kick-off minus yours, in seconds (within ±900)."},"teams_reversed":{"type":"boolean","description":"True when your home team is our away team: swap home and away selections before comparing."},"kickoff_source":{"type":"string","enum":["own","fallback"],"description":"Where kickoff came from (D2.4, appended): 'own' = the kick-off at least two independent bookmakers agree on (within 5 minutes, a majority of those still listing the match); 'fallback' = our cross-reference schedule, used when fewer than two agree or they disagree with it by more than 10 minutes (then the close is withheld)."},"event_id":{"type":["string","null"],"pattern":"^evt_[0-9A-HJKMNP-TV-Z]{26}$","description":"The same match's event id in the OddsIntel Odds API (evt_ + 26 characters). Appended 2026-10-23. Null when we hold none."}}},"FixturesMeta":{"type":"object","required":["count","kickoff","window_s","data_as_of","contract_version","plan","lookup"],"properties":{"count":{"type":"integer"},"kickoff":{"type":["string","null"],"format":"date-time","description":"Your kick-off, as UTC. Null on an id lookup."},"window_s":{"type":["integer","null"],"description":"Search window either side of kickoff, seconds (900). Null on an id lookup."},"data_as_of":{"type":["string","null"],"format":"date-time"},"contract_version":{"type":"string"},"plan":{"type":"string","enum":["sample","starter","pro","business","trial"],"description":"The plan of the key used."},"lookup":{"type":"string","enum":["teams","fixture_id","event_id"],"description":"How the call looked the fixture up: teams (kick-off + team names) or the id parameter sent."}}},"FixturesResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","maxItems":5,"items":{"$ref":"#/components/schemas/Fixture"}},"meta":{"$ref":"#/components/schemas/FixturesMeta"}}},"ChangelogRow":{"type":"object","required":["id","fixture_id","kickoff","market","line","selection","old_hash","new_hash","reason","first_published_at","changed_at"],"properties":{"id":{"type":"integer","description":"Changelog id, increasing. Page with after=."},"fixture_id":{"type":"string","format":"uuid"},"kickoff":{"type":["string","null"],"format":"date-time","description":"The kick-off served after the change."},"market":{"type":"string","enum":["1x2","ou","btts","ah"]},"line":{"type":["number","null"]},"selection":{"type":"string"},"old_hash":{"type":["string","null"],"pattern":"^[0-9a-f]{64}$","description":"close_hash before the change (null when it was withheld)."},"new_hash":{"type":["string","null"],"pattern":"^[0-9a-f]{64}$","description":"close_hash after the change (null when it is withheld, re-opened or gone)."},"reason":{"type":"string","enum":["revised","kickoff_moved","withheld","reopened","restored","removed"],"description":"revised: another closing price or close time at the same kick-off. kickoff_moved: the kick-off changed, so the close was taken again at the new time. withheld: still final but now withheld (match not played, kick-off date disputed, or the match started early). reopened: served again as a pre-kick-off figure (the kick-off moved later). restored: a withheld close is served again. removed: no longer served at all."},"first_published_at":{"type":"string","format":"date-time","description":"When we first recorded this close as served. Closes served before the ledger started (meta.ledger_from) show the ledger's start."},"changed_at":{"type":"string","format":"date-time"}}},"ChangelogMeta":{"type":"object","required":["count","since","next_after","ledger_from","ledger_checked_at","contract_version","plan"],"properties":{"count":{"type":"integer"},"since":{"type":"string","format":"date-time"},"next_after":{"type":["integer","null"],"description":"More rows: call again with after=<this>. At most 1000 rows per answer."},"ledger_from":{"type":["string","null"],"format":"date-time","description":"When we started recording published closes. A close published since then that is not in the changelog has not changed."},"ledger_checked_at":{"type":["string","null"],"format":"date-time","description":"The last completed check of every recent close (every 2 minutes; older days daily)."},"contract_version":{"type":"string"},"plan":{"type":"string","enum":["sample","starter","pro","business","trial"],"description":"The plan of the key used."}}},"ChangelogResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ChangelogRow"}},"meta":{"$ref":"#/components/schemas/ChangelogMeta"}}},"WebhookDelivery":{"type":"object","required":["id","event_id","type","fixture_id","status","attempts","last_attempt_at","last_http_status","last_error","delivered_at","created_at"],"properties":{"id":{"type":"integer","description":"= the OddsIntel-Delivery header."},"event_id":{"type":"string","format":"uuid","description":"= the notice's id."},"type":{"type":"string","enum":["close.ready","close.changed","ping"]},"fixture_id":{"type":["string","null"],"format":"uuid"},"status":{"type":"string","enum":["pending","delivered","failed","expired","skipped"],"description":"pending (waiting or between retries), delivered (your server answered 2xx), failed (6 attempts without a 2xx), expired (not delivered within 24 hours), skipped (webhook disabled, key revoked or plan changed)."},"attempts":{"type":"integer"},"last_attempt_at":{"type":["string","null"],"format":"date-time"},"last_http_status":{"type":["integer","null"]},"last_error":{"type":["string","null"]},"delivered_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}},"Webhook":{"type":"object","required":["url","status","events","consecutive_failures","disabled_at","disabled_reason","created_at","updated_at","recent_deliveries"],"properties":{"url":{"type":"string","format":"uri"},"status":{"type":"string","enum":["active","disabled"],"description":"disabled after 20 failed attempts in a row; PUT again to switch it back on."},"events":{"type":"array","items":{"type":"string","enum":["close.ready","close.changed"]}},"consecutive_failures":{"type":"integer"},"disabled_at":{"type":["string","null"],"format":"date-time"},"disabled_reason":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"recent_deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"},"description":"The last 20 deliveries, newest first (kept 30 days)."},"secret":{"type":"string","pattern":"^whsec_[0-9a-f]{64}$","description":"PUT answer only. The signing secret: store it now, it is never shown again. PUT again for a new one."}}},"WebhookRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","maxLength":500,"description":"https only, a public host name (no IP address, no user name or password). We never follow redirects."}}},"WebhookResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"oneOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"null"}]},"meta":{"type":"object","required":["contract_version","plan"],"properties":{"contract_version":{"type":"string"},"plan":{"type":"string","enum":["sample","starter","pro","business","trial"],"description":"The plan of the key used."}}}}},"WebhookEvent":{"type":"object","required":["id","type","created_at","contract_version","data"],"properties":{"id":{"type":"string","format":"uuid","description":"Notice id: the same notice can arrive twice (a retry after a timeout), so de-duplicate on it."},"type":{"type":"string","enum":["close.ready","close.changed","ping"]},"created_at":{"type":"string","format":"date-time"},"contract_version":{"type":"string"},"data":{"type":"object","properties":{"fixture_id":{"type":"string","format":"uuid"},"kickoff":{"type":["string","null"],"format":"date-time"},"markets":{"type":"array","items":{"type":"string","enum":["1x2","ou","btts","ah"]},"description":"close.ready: the markets whose closes are new in this notice (a later notice can add more). close.changed: the markets with a changed close."},"n_closes":{"type":"integer","description":"Published final closes this fixture has now."},"fixture_hash":{"type":["string","null"],"pattern":"^[0-9a-f]{64}$","description":"sha256 of the fixture's close_hash values, sorted, one per line. Equal to yours = you hold exactly what we serve."},"closes_url":{"type":["string","null"],"description":"The /close call for the kick-off date."},"n_changes":{"type":"integer","description":"close.changed only."},"changes":{"type":"array","description":"close.changed only, at most 50: market, line, selection, old_hash, new_hash, reason (as in the changelog).","items":{"type":"object"}},"message":{"type":"string","description":"ping only."}}}}}}},"webhooks":{"closeNotice":{"post":{"tags":["Webhooks"],"operationId":"closeNotice","summary":"The notice we POST to your URL","description":"Headers: OddsIntel-Event (the type), OddsIntel-Delivery (the delivery id), OddsIntel-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of '<t>.<raw body>' with your secret>. Check the signature on the RAW body and refuse a t more than 5 minutes old. Answer 2xx within 5 seconds; anything else is retried after 1 min, 5 min, 30 min, 2 h and 6 h (6 attempts), then given up. After 20 failed attempts in a row the webhook is disabled. Redirects are not followed. The same notice can arrive twice: de-duplicate on id.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"},"example":{"id":"6b1f0f8e-2b9c-4d5e-8a3f-1c2d3e4f5a6b","type":"close.ready","created_at":"2026-10-20T19:17:02Z","contract_version":"v1","data":{"fixture_id":"8f0c6a2e-4b1d-4c7a-9e3f-2d5b8a1c0e47","kickoff":"2026-10-20T19:00:00Z","markets":["1x2","ou","btts","ah"],"n_closes":23,"fixture_hash":"0f8b6c0a6c2b5f0a3e9d7c1b2a4f6e8d0c2b4a6f8e0d2c4b6a8f0e2d4c6b8a0f","closes_url":"https://data.oddsintel.app/v1/close?date=2026-10-20"}}}}},"responses":{"200":{"description":"Any 2xx = delivered."}}}}}}