Create a key
Sign up with your email. The Free key is generated right away, no card, and you see it only once: store it where you keep your secrets.
export ODSS_KEY="odss_live_…"
Sports odds API · for developers and platforms
One JSON format for football, tennis, basketball and every other sport on the board. Every price carries its own last_update, and the feed's freshness is measured and public.
curl "https://odss-api.com/api/v1/mock/odds?sport=calcio&market=1x2&limit=2"
from odss_api import OdssApi
api = OdssApi.mock()
page = api.odds(sport="calcio", market="1x2", limit=2)
import { OdssApi } from "./odss-api";
const api = OdssApi.mock();
const page = await api.odds({ sport: "calcio", market: "1x2", limit: 2 });
Real feed: /api/v1/odds with the x-api-key header · SDK Python · TypeScript
{
"mock": true,
"notice": "Dati SINTETICI a scopo di sviluppo: prezzi inventati, nessun rapporto con i listini reali dei book. Feed vero: /api/v1/* con chiave.",
"count": 7, "offset": 0, "returned": 2, "state": "prematch",
"odds": [
{
"event_id": "mock_39b522d4",
"event": "Juventus - Inter",
"sport": "calcio", "league": "Serie A",
"market": "1x2",
"bookmakers": [
{
"key": "mockbook_it_1", "country": "IT", "playable_it": true,
"is_exchange": false, "family": "mockplat_a",
"outcomes": { "HOME": 2.27, "DRAW": 3.14, "AWAY": 3.06 },
"last_update": "2026-09-23T23:59:00.000Z"
},
{
"key": "mockbook_it_2", "country": "IT", "playable_it": true,
"is_exchange": false, "family": "mockplat_a",
"outcomes": { "HOME": 2.8, "DRAW": 3.45, "AWAY": 2.46 },
"suspended": [],
"last_update": "2026-09-23T23:58:23.000Z"
},
{
"key": "mockbook_it_3", "country": "IT", "playable_it": true,
"is_exchange": false, "family": "mockplat_a",
"outcomes": { "HOME": 3.9, "DRAW": 2.42 },
"suspended": ["AWAY"],
"last_update": "2026-09-23T23:57:46.000Z"
}
]
}
]
}
The notice field is always in Italian, whatever the page language.
Age of the Italian prices you receive, over the last 24 hours. How we measure it
How it works
Sign up with your email. The Free key is generated right away, no card, and you see it only once: store it where you keep your secrets.
export ODSS_KEY="odss_live_…"
Filter by sport, league and market using the feed's names. A parameter that does not exist returns 400 and suggests the right one.
curl "https://odss-api.com/api/v1/odds?sport=calcio&market=1x2" \
-H "x-api-key: $ODSS_KEY"
Every price says when it was read (last_update); every response says how many requests you have left and when your quota resets.
X-RateLimit-Limit: 100000
X-RateLimit-Remaining: 99870
X-RateLimit-Reset: 1791763200
Freshness
We don't promise real time: we measure how old the odds are and publish it, so you decide how far to trust a price.
Every 2 minutes, on the snapshot the API is serving, we measure the age of every pre-match price of the Italian books (now − last_update): it is the age of the prices you receive, not the age inside the engine. p50 and p95 are the median of the samples of the last 24 hours; the dashed mark is the cycle with which the engine re-reads a book.
It is not uptime, which we don't claim, and it is not the latency of your request: it is how old the price you receive is. One limit, stated: last_update is the end of the book's read, and in detail reads that last minutes the first rows read are somewhat older than their timestamp.
Product
REST to query and streaming to receive, with documentation that machines can read too. Price history is not active yet.
Same names for sports, markets and outcomes across ADM, international and exchange books. A market we can't map with certainty comes out as other:<label>, never guessed.
| bookmakers[].key | HOME | DRAW | AWAY | last_update |
|---|---|---|---|---|
| mockbook_it_1 | 2.27 | 3.14 | 3.06 | 23:59:00Z |
| mockbook_it_2 | 2.80 | 3.45 | 2.46 | 23:58:23Z |
| mockbook_it_3 | 3.90 | 2.42 | suspended | 23:57:46Z |
| mockbook_it_4 | 3.16 | 2.64 | 2.58 | 23:57:09Z |
| mockbook_it_5 | 2.43 | 2.42 | 3.66 | 23:56:32Z |
| mockbook_it_6 | 2.36 | 2.31 | 4.49 | 23:55:55Z |
| mockbook_it_7 | 2.11 | 3.79 | 2.81 | 23:55:18Z |
| mockbook_uk_1 | 1.97 | 2.87 | suspended | 23:24:41Z |
| mockbook_sharp | 4.02 | 2.17 | 3.22 | 23:54:04Z |
| mockbook_exchange | 2.89 | 3.30 | 2.44 | 23:53:27Z |
An outcome the book prices but is not accepting right now sits in suspended, not in outcomes: «not priced» and «priced but closed» stay two different facts.
Dozens of Italian ADM-licensed operators, plus international reference books and exchanges. The active list is on /api/v1/bookmakers, with a key: no list of brands to take on trust.
Response fields of /api/v1/bookmakers: the endpoint takes no parameters. On /api/v1/odds, country and playable_it also work as filters.
On /api/v1/stream you only receive the changed and removed records, no polling: with state=live, live usually within 10-30 seconds. The whole pre-match does not arrive reliably today (a full round weighs hundreds of MB): it arrives with narrow filters, in waves; for all of the pre-match use /odds.
id: mock.42 event: odds data: {"changed":[…],"removed":[]} : ping
GET /api/v1/mock/stream Sample stream, no key needed · synthetic data
Not active yet: today /odds/history answers empty (history_enabled: false) and the call counts against your quota. Once it is: one row every time a book changes a price, and once the match has started the first row is the closing price.
/coverage counts the brands with a valid price right now; /coverage/matrix tells you, book by book and sport by sport, which markets they price.
Brands with a valid price right now, by region · GET /api/v1/coverage
Changes are POSTed to your URL, signed with HMAC-SHA256; a large delivery arrives in several numbered pages.
POST <url> X-Odss-Timestamp: <unix> X-Odss-Signature: t=<unix>,v1=<hex> X-Odss-Page: 1/3 {"page": 1, "pages": 3, "changed": […], "removed": […], "changed_total": <n>, …}
An agent discovers the tools with no key and no quota used; the key is only needed to call them.
| JSON-RPC method | Key and quota |
|---|---|
| initialize | no |
| tools/list | no |
| tools/call | yes |
Python and TypeScript with no dependencies, with pagination and typed errors. OpenAPI, Postman and llms.txt for whoever integrates with an agent.
Comparison
Where we stand vs global APIs and the market average. No names, no promises.
| Feature | odss-api | Market average |
|---|---|---|
| Italian ADM books | Yes, verifiable. /coverage public, /bookmakers with a key; the age of the prices you receive is measured and public (p50/p95 at /status). | Few, often per book. Built for foreign books (US/UK/offshore): ADM licensees, when present, are few and often priced per book. |
| How you pay | Per account. Monthly quota + rate/min: more books or markets per event don't add cost. | Per book or credits. Markets×regions credits: on multi-book the cost explodes. |
| Cross-book | Deterministic. One event_id shared by all markets of a fixture: native comparison, no heuristic matching. | Often heuristic. Proprietary odds or non-deterministic matching. |
| Freshness | Per book, in the clear. ISO last_update on every book: you know how recent each leg is. | Often missing. Not exposed per single book. |
| Agent-ready | Yes, open standards. OpenAPI 3.1, llms.txt, JSON-LD, /.well-known/api-catalog: an agent finds and reads it on its own. | Rarely. Docs behind login or blocked crawlers, no agentic standard. |
| Price & onboarding | Public pricing. Free tier, configurable self-serve Enterprise. A key in a minute. | Often sales-led. Opaque Enterprise, or self-serve with few Italian books, often priced per book. |
| Targeted keys | By sport or league. E.g. a «soccer only» key, with no key-count limit. | Not documented. |
Coverage
Not a catalogue: sports, leagues, events and books read right now from the public /api/v1/leagues endpoint. They change with the board, and you can check them without a key.
We don't exclude any league. We track the main ADM bookmakers — the exact list is at /api/v1/bookmakers — and show everything they price: if a book in the feed quotes a match, you see it here too — from the big European leagues to minor divisions and international competitions. What appears depends on season and timing: no matches means bookmakers don't price them, so they don't show. The list below is the schedule at this moment.
Loading the schedule…
The named list of the books active right now is at GET /api/v1/bookmakers, with the country, playable_it, is_exchange and source fields: it requires an API key (the Free one takes a minute, no card). Without a key, GET /api/v1/leagues — the source of the counts above — and GET /api/v1/coverage are public.
Pricing
SSE streaming comes with Pro, webhooks with Enterprise. Start free and cancel anytime.
On every plan, Free included:
To explore the API. Forever, no card.
0 € forever, no card
500 requests/month · 30/min
Example: writing and testing the integration, sandbox included.
For personal projects and small bots.
19 € per month + VAT
10,000 requests/month · 60/min
Example: one league read every 5 minutes, about 8,600 requests a month.
For bots and production integrations.
79 € per month + VAT
100,000 requests/month · 120/min
Example: three leagues including live, read every 90 seconds: about 86,000 requests a month.
For platforms and high volumes.
159 € per month + VAT
500,000 requests/month · 600/min
Example: ten leagues including live, read every 90 seconds (about 288,000 requests a month), or the stream.
For platforms, companies and resellers: configure volume, rate and license — the price updates by itself and you activate at checkout, no negotiation.
Authorises you to show the data to your product's end users — websites, apps, dashboards, widgets, comparison tools (without it, use stays internal). It does not authorise creating or commercialising surebet/arbitrage services, value betting or equivalent platforms, nor redistributing the API or the raw data: those need a dedicated commercial licence. Read the licence
Prices shown are exclusive of VAT. Applicable tax is added at checkout based on your country and tax status: 22% for Italy (including businesses with an Italian VAT number); reverse charge, 0% tax, for businesses in other EU Member States with a valid VAT number; outside the scope of Italian VAT for non-EU customers. The total amount you pay is the one shown in the checkout summary, which you confirm before placing your order (§4 of the Terms). The quota covers 30 days from the first request of the period.
Commercial solutions
Normalized odds are the first tier. If what you want to sell are the arbitrages themselves, you need a different tier: the surebets already computed, with the right to resell them included in the subscription.
Tier 1 · self-serve
Normalized odds and data. From the Free plan to Enterprise, public pricing, instant activation.
See pricingTier 2 · API plan
For whoever builds a product of their own on top of it. It does not return odds alone: it returns the surebets already identified and computed by our engine, recomputed on every scan cycle. If you have nobody to resell them to, you do not need it: surebett.app is enough.
€950 per month, exclusive of VAT · resale licence included · cancel in one click from the portal.
The fields listed are those provided where available: the response carries what the engine actually computed for that event. The resale licence is included in the subscription: you may use the computed surebets in a commercial product of your own, aimed at your end users, within the limits of the declared use and of the Terms. It does not cover the White Label, which remains a separate contract.
Activated as soon as the payment goes through, cancel in one click from the portal. It is a plan of its own and does not stack on a listed plan: if the account already has a paid one active, it has to be cancelled first. By proceeding you enter into the Surebet API Commercial License, within the limits of the use you declared above, and accept the Terms of service (§5).
Tier 3 · under contract
For companies that do not want to build the platform themselves: the complete Surebet platform, delivered as a service under your own brand.
€2,000 per month, exclusive of VAT · activation and term as per the White Label contract.
The software, the engine and the source code remain our property and are granted for use under the White Label contract.
The «Display Redistribution» licence of the API plans authorises you to show the odds inside your product: it does not authorise creating or commercialising surebet/arbitrage services, value betting or substantially equivalent platforms, nor redistributing the API or the raw data. Those uses require the Surebet API Commercial License or the White Label contract — see the commercial licences and the Terms, §5.
More questions? Write to [email protected] or open a ticket: a person replies, by email.
Yes: the API exposes the odds of the Italian licensees currently in the feed, filterable with the bookmakers parameter. The list active right now is at GET /api/v1/bookmakers, which requires an API key (the Free one takes a minute, no card); without a key, /api/v1/leagues and /api/v1/coverage are public. Every quote carries its last_update.
Yes: the API returns standard JSON, so it goes straight into Postgres, SQLite, pandas or Google Sheets. With the Python and TypeScript SDKs it takes a few lines; the Free plan (500 requests/month) is enough to try, no card.
Here: Free 500 requests/month without a card, Hobby €19/month + VAT (10,000), Starter €79/month + VAT (100,000), Pro €159/month + VAT (500,000), Enterprise from €199 + VAT. All amounts are exclusive of VAT: for an Italian consumer 22% is added. Businesses in other EU Member States with a valid VAT number are subject to reverse charge (0% tax); for non-EU customers the supply is outside the scope of Italian VAT. The total is always shown in the checkout summary, which you confirm before the order (§4 of the Terms). Same coverage on every plan: you only pay for volume.
The age of the prices you receive is measured, not promised: p50 and p95 over the last 24h are public on the status page, measured on the served snapshot. SSE streaming for push; in-play has its own feed, renewed every few seconds.
Internal use — your own models, bots, analysis, tools — is included in every plan. Showing the data to your product's end users requires the Display Redistribution licence (Enterprise add-on): it authorises display only. If what you want to sell are the surebets — arbitrage, value betting or equivalent services — you need the Surebet API, a plan of its own at €950/month + VAT, with the licence to resell them included in the subscription and the declared-use form required: see how it is activated. If you have nobody to resell to, you do not need it: surebett.app is enough. The complete platform under your own brand remains the White Label, under contract — see Surebet API and White Label.
The engine re-reads the Italian books on a continuous cycle, but what matters is the age of the prices you receive: we measure it on the served snapshot and publish it on the status page, with p50 and p95 over the last 24 hours (October 2026: p50 of 11-14 minutes during the day, about 18 in the evening). The Italian content of the feed is renewed every 7-12 minutes on average, live every few seconds. You don't have to take our word for it: every quote in the response carries its last_update.
The Free plan is forever and has the same coverage as the paid plans: so you can check the data before paying. If you need more volume for an evaluation, write to [email protected].
Same engine, two products: odss-api sells the raw normalized odds to build on; surebett.app is the finished product, with arbitrages already computed and balanced stakes.
For developers
Base URL https://odss-api.com. Authenticate with the x-api-key: odss_live_… header or Authorization: Bearer. JSON responses, European decimal odds.
Every endpoint has a twin under /api/v1/mock/ that answers right now, with no signup and no quota consumed: same response shape, same parameters, same strict validation as the real feed. The prices are synthetic — mock: true, header X-Odss-Mock: 1, ids prefixed mock_ — they are there to write and test your code, not to bet on.
curl "https://odss-api.com/api/v1/mock/odds?sport=calcio&limit=3"
Available: /mock/odds · /mock/odds/history · /mock/sports · /mock/bookmakers · /mock/leagues · /mock/status · /mock/stream (SSE) · /mock/ticker (one frame of sample 1X2 odds every 5 s, no parameters). The seed parameter makes the payload reproducible: use it in CI instead of burning quota on every run.
On the REAL feed these are public with no key too: /api/v1/leagues, /api/v1/coverage, /api/v1/status and /api/v1/pulse.
curl "https://odss-api.com/api/v1/odds?sport=calcio&league=serie%20a&limit=5" \
-H "x-api-key: odss_live_..."
const r = await fetch(
"https://odss-api.com/api/v1/odds?sport=calcio&league=serie%20a&limit=5",
{ headers: { "x-api-key": "odss_live_..." } }
);
const { count, odds } = await r.json();
import requests
r = requests.get(
"https://odss-api.com/api/v1/odds",
params={"sport": "calcio", "league": "serie a", "limit": 5},
headers={"x-api-key": "odss_live_..."},
)
data = r.json() # { count, offset, returned, state, odds: [...] }
Current cross-book odds, grouped by event, market and line. Each record lists the bookmakers with their outcomes and the timestamp of their last update.
| Parameter | Description |
|---|---|
| sport string | Exact sport filter (e.g. calcio, tennis, basket). Values: see /api/v1/sports. |
| market string | Canonical market: 1x2, ou, btts, dc, ah (Asian handicap), eh (European handicap), moneyline… |
| bookmakers string | CSV list of book keys (e.g. snai,bet365). Values: see /api/v1/bookmakers. |
| league string | Case-insensitive substring filter (e.g. serie a). Values: see /api/v1/leagues. |
| event_id string | A single event, by exact id. |
| state prematch | live | all | prematch (default), live (in-play, with current score and minute) or all (both). With live, if the in-play engine is off the list is empty and live_enabled=false. If the key has a feed scope (prematch-only or live-only), a state outside the scope returns 403 and all degrades to the allowed state. |
| limit integer | Records per page. Default 500, max 5000. |
| offset integer | Start index for pagination. Default 0. |
| content match | player | all | match = fixture lines only; player = player props only; all (default) = both. Props are included on every plan: the parameter narrows the response, it does not unlock anything. |
| player string | Case-insensitive substring of the player name; implies content=player. |
| commence_from · commence_to date-time | Window on the event start (ISO 8601), bounds included. With a window, records without commence_time are excluded; an unreadable date, or from after to, is a 400. |
| playable_it boolean | true = keep only the bookmakers playable from Italy (ADM licence) inside each record; false = only the others. Records left with no bookmaker are dropped. |
| country string | CSV of licensing countries (ISO 3166-1 alpha-2, or world): only those bookmakers stay inside each record, e.g. IT or GB,SE. |
| after string | STABLE cursor pagination: after= empty for the first page, then the next_after of the response until it is null. The order does not depend on the snapshot refresh, so a full sync has no holes or duplicates. Mutually exclusive with offset; combinable with changed_since. |
| format full | compact | compact = the same data without repetition (bookmaker metadata once in books, event fields once in events): about 40% fewer bytes. Default full, the unchanged schema. |
| changed_since string | DELTA sync, on every plan. The first time send changed_since= empty: full response plus a delta_token. Then send the token back: only the records that changed or appeared (in odds, in their current state) and the ones that disappeared (in removed), with a new delta_token. An old token (beyond about 10 minutes) or one from another server start answers 410 resync_required: start again with an empty changed_since=. A record whose price did not change does not come back in the delta: its last_update stays the one of the last change, even if the book was read again later. |
| Field | Description |
|---|---|
| count | Total records matching the filters (across all pages). |
| offset · returned | Echo of the requested offset and the records actually returned in this page. |
| odds[].event_id | Deterministic event id: identical for every market of the same fixture and stable for the whole prematch window. |
| odds[].event | Normalized event name (Home - Away). |
| odds[].sport · league | Sport and league/competition of the event. |
| odds[].home_team · away_team | The two teams, when available. |
| odds[].commence_time | Event start, ISO 8601 UTC. |
| odds[].market · line · scope · period | Canonical market, optional line (e.g. 2.5), scope (e.g. team) and period (e.g. 1st half). |
| odds[].state · score · minute | state = prematch or live. For live: current score (e.g. "1-0") and minute (e.g. "63'"). Same event_id as prematch to correlate the fixture across the two states. |
| …bookmakers[].key | Bookmaker key, with country, playable_it and is_exchange. |
| …bookmakers[].outcomes | Outcome → decimal odds map (e.g. HOME: 2.10). |
| …bookmakers[].suspended | Outcomes this book prices but is not taking bets on right now. They are not in outcomes (a suspended outcome has no playable price) and are named here instead, so that «the book does not offer this outcome» and «it offers it and is closed right now» stay two distinct facts. Optional field, and its absence means something: if missing, that book does not publish the state; [] means it was declared and nothing is suspended. Never read an absent field as an empty list. |
| …bookmakers[].last_update | ISO timestamp of that book's last successful odds update. |
| …bookmakers[].family | Platform the price list comes from. Brands of the same family can serve the SAME list: counting them as independent lists invents arbitrages that do not exist. Same family does not always mean same price: it is a correlation warning, not an exclusion rule. |
| odds[].player · player_id | Only on player-prop rows (absent, not null, on fixture rows): player is part of the row identity; player_id is the book's stable id for that player, to follow them across refreshes (not a cross-book id). |
Repeat the call increasing offset by returned, as long as offset + returned < count. The board is live: count may shift slightly between pages.
GET /api/v1/odds?sport=calcio&limit=1000&offset=0
GET /api/v1/odds?sport=calcio&limit=1000&offset=1000
# … while offset + returned < count
The sports currently available in the feed (the values for the sport parameter).
The bookmakers active in the feed, each with these fields.
| Field | Description |
|---|---|
| country | ISO 3166-1 alpha-2 code of the licensing country (IT, GB, US, SE…), or world when none is attributed. |
| playable_it | true only for books holding an Italian ADM licence, i.e. playable from Italy; false for every other book in the feed, from sharp references like Pinnacle to the international brands. |
| is_exchange | Exchange, with commission on winnings. |
| feed_keys | The keys this operator's prices are served with in /odds: the brand and, if any, its dedicated market lane (e.g. <brand>-corners). One operator, one row. |
| derived_from | Only when the operator's pre-match prices are copied from another one: the key of the source book, like derived_from on every quote (derived_states says in which state). |
| source | The price list's channel type: direct | partner_feed. |
The current schedule, aggregated: for each sport the priced leagues, with event and bookmaker counts. Useful to discover values for the league filter.
Server-Sent Events: only the changed and removed records arrive, no polling. With state=live, live usually arrives within 10-30 seconds (measured median about 15 s). Today's limit, measured on 8 October 2026: with state=all a full pre-match round weighs hundreds of MB and the connection drops before it is received; the stream carried no change 79% of the time, with a resync about every 12 minutes. Pre-match only arrives with narrow filters (sport, market, league), in waves, when the served snapshot is renewed. We are rewriting the delivery into small pages with backpressure; until then, for all of the pre-match use the paginated snapshot of /odds.
| Item | Description |
|---|---|
| hello | On connect. |
| odds | The CHANGED records (same schema as /odds, each with its own state) and the removed ones. |
| sport · state | Optional parameters (state: prematch | live | all). Initial snapshot via GET /odds?state=…. |
| Cadence | That of the served snapshot: live is renewed every few seconds, pre-match every 4-6 minutes on average (the Italian content every 7-12). last_update is authoritative, with one limit: when the price does not change the record is not sent again, so the last_update you hold stays the one of the last change even if the book was read again later. The real age of the served pre-match is on /status (served_age). |
| Limits | Max 3 concurrent streams; the connection counts as 1 quota request. |
Not active yet. Capture is switched off today: every call answers 200 with count: 0 and history_enabled: false, and counts against your quota. Once active: odds change history for an event, one row every time a bookmaker moves a price on a group. Filters event_id (required), book, market, from/to (ISO), limit. Newest first: for a started event the first row is its closing line. Honours the key scope.
curl "https://odss-api.com/api/v1/odds/history?event_id=a3f9c1e07b2d4865&book=snai" \
-H "x-api-key: odss_live_..."
Over/Under middles computed from the current feed: OVER on a low line at one book, UNDER on a high line at another. If the total lands inside the window both bets win. Fields middle_window, hit_return_pct, miss_return_pct, safe (no loss even when it misses). Filters: sport, min_return, safe, limit.
Freshness rule: a price older than stale_after_seconds (900s by default) stays in /odds responses with its last_update, but is never paired into a middle: a stale leg probably no longer exists on the bookmaker site. The applied threshold and the number of discarded legs are in the response.
Limits of safe, today: some market families are not yet keyed by unit (in tennis and volleyball a set total can sit next to a games or points total), some 'Home - Away' pseudo-events of one platform are served like matches, and two matches with the same name can share an event_id. In those cases safe: true is not a real middle: before acting, check that both legs are the same match, the same unit and the same start time. The response says so in the safe_note field.
curl "https://odss-api.com/api/v1/middles?sport=calcio&safe=true" \
-H "x-api-key: odss_live_..."
How many bookmaker brands have a valid price in the current feed, per region (Italy, USA, UK, world). It matches the entries of /api/v1/bookmakers: both count an operator once, with its dedicated market lanes (e.g. <brand>-corners) under the brand; in /bookmakers the keys its prices are served with are in feed_keys.
For every bookmaker, sport by sport: the normalized markets it prices right now, with the lines seen. It exists to keep apart three things a flat list confuses.
A market not listed under an attivo bookmaker is a structural absence: that book does not price it, and tomorrow will be the same. The bookmakers in live_only_bookmakers have rows but not in the pre-match lane, so about their pre-match markets this says nothing; if a feed lane did not answer, incomplete_lanes names it and the coverage is partial, not absent.
And a market the engine cannot map with certainty is never guessed: it goes out as other:<label> and here is only counted (other_markets), because two books label the same thing differently and comparing those labels would be wrong — the ones many books spell identically are in other_markets_common. Scope prematch, recomputed at most every 10 minutes (generated_at).
The feed heartbeat, aggregate numbers only (no prices, no book names): it is what the pages of this site poll on their own. No parameters, Cache-Control: public, max-age=5, its own per-IP limit (60 per minute).
| Field | Description |
|---|---|
| prematch_it_at | The instant the snapshot containing the current Italian lane was published: never newer than the pages being served. |
| beats | The latest arrivals of a new Italian lane in the served snapshot, up to 30, collected since the server started (beats_since). |
| freshness_24h | For every half hour of the last 24 hours: served_p50 and served_p95, the age of the Italian prices you were receiving (sampled every 2 minutes on the served snapshot), and p50/p95, the age inside the engine. served carries the same percentiles over the 24 hours. A half hour without samples stays empty, never estimated. |
MCP server (JSON-RPC 2.0) for AI agents and connectors (Claude, ChatGPT…). Auth with the same key (Authorization: Bearer odss_live_… or x-api-key). Tools: get_odds (with state=live), list_sports, list_bookmakers, list_leagues. Only tools/call uses quota; the handshake is free. Stateless: every request is self-contained.
curl -X POST https://odss-api.com/api/mcp \
-H "Authorization: Bearer odss_live_..." -H "content-type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"get_odds","arguments":{"sport":"calcio","state":"live","limit":5}}}'
Register an https URL and receive odds changes via POST, with the same {changed, removed} payload as the stream.
| Item | Description |
|---|---|
| POST /api/account/webhooks | From the dashboard or via API, with your account session. Body {url, sport?, state?}; the secret is shown once, max 20 per account. |
| X-Odss-Signature | t=<unix>,v1=<hex> = HMAC-SHA256 of «{timestamp}.{body}» with the webhook secret. Each delivery is signed: verify it, reject old timestamps. |
| X-Odss-Page | page/total: one delivery can arrive as several consecutive POSTs, each signed on its own. Every body carries page, pages, changed_total and removed_total, so the first page already tells you how much is coming. |
| Rules | Respond 2xx. https only, redirects not followed; endpoints that repeatedly fail auto-disable. Don't rate-limit the endpoint: a rejected page stops the ones after it. |
Every authenticated response includes X-RateLimit-Limit (your plan's monthly quota), X-RateLimit-Remaining and X-RateLimit-Reset (unix seconds when the window reopens; on the monthly-quota 429 the same instant is in the body as retry_at). The quota covers a 30-day window that starts with the first request of the period: when it ends, the counter resets and a new window starts with the next request.
| Status | Description |
|---|---|
| 400 | Invalid request: unknown parameter, value outside the allowed set (e.g. state) or missing required parameter (e.g. event_id on /odds/history). The response body says which. |
| 401 | Missing, invalid or revoked key. |
| 403 | Out of scope: suspended account, feed outside the key's scope (e.g. live with a prematch-only key) or plan-gated feature (stream: Pro and Enterprise; webhooks: Enterprise). |
| 429 | Burst: per-minute requests of your plan exceeded. Retry-After: 60 header. |
| 429 | Monthly quota exhausted: upgrade or wait for the window. |
| 502 | Feed temporarily unavailable: retry with backoff. |
On every /api/v1 endpoint a parameter we don't recognize is never ignored: if you write bookmaker instead of bookmakers you get a 400 with the wrong name, the list of the ones allowed on that endpoint and, if it's a typo, a suggestion for the right name. The comparison is exact: even Sport with a capital letter is rejected. The catalog endpoints (/sports, /bookmakers, /leagues, /coverage, /status) accept no parameters. We prefer an error right away over a 200 response with unfiltered data that looks correct: the 400 is deterministic, it should be fixed rather than retried, and it does not use quota.
The same rule applies to the tool arguments on the MCP server (POST /api/mcp): a name that doesn't exist, or a wrong type such as a list where a CSV string is expected, gets a JSON-RPC -32602 error instead of being ignored. The error.data field carries the allowed names, so the agent corrects itself.
curl "https://odss-api.com/api/v1/odds?bookmaker=snai" -H "x-api-key: odss_live_..."
HTTP/1.1 400 Bad Request
{
"error": "Parametro sconosciuto: \"bookmaker\" (forse intendevi \"bookmakers\"?). Ammessi su questo endpoint: after, bookmakers, changed_since, commence_from, commence_to, content, country, event_id, format, league, limit, market, offset, playable_it, player, sport, state"
}
To generate clients, SDKs or use the API from an AI agent: the OpenAPI 3.1 contract and a concise LLM guide are public and always in sync.
| Resource | Description |
|---|---|
| /openapi.json | OpenAPI 3.1 contract (endpoints, parameters, schemas, x-api-key auth). |
| /llms.txt | Markdown guide for LLMs (llmstxt.org): summary and links to resources. |
| /postman_collection.json | Postman collection: import it and set the x-api-key variable to try the endpoints right away. |
| /sdk/odss_api.py | Official Python SDK (single-file, zero dependencies): typed client with automatic pagination and live support. |
| /sdk/odss-api.ts | Official TypeScript SDK (single-file, zero dependencies): types, async iterator and an SSE stream helper. |
| /.well-known/api-catalog | API catalog (RFC 9727, JSON linkset). |
Internal use (models, bots, analysis, backtests) is included in every plan. Redistributing the data — e.g. showing it to your product's end users — requires the Display Redistribution licence (Enterprise add-on, +€120/month), which covers display only. Creating, powering or commercialising surebet/arbitrage services, value betting or equivalents requires the Surebet API instead, the plan that includes the licence to resell the computed surebets: €950/month + VAT. Redistributing the API or the raw data stays outside every plan: for that, write to [email protected].
When you're ready, the Free key takes a minute. No card.
curl https://odss-api.com/api/v1/mock/odds