Italian bookmaker odds,
in one API
Normalized odds from dozens of bookmakers — including the Italian books global APIs lack — in a stable JSON format. For your models, bots and tools.
# one request, all the odds for an event curl "https://odss-api.com/api/v1/odds?sport=calcio&league=serie%20a" \ -H "x-api-key: odss_live_..." { "count": 118, "offset": 0, "returned": 118, "state": "prematch", "odds": [{ "event_id": "a3f9c1e07b2d4865", "event": "Inter - Milan", "league": "Serie A", "market": "1x2", "commence_time": "2026-08-23T18:45:00Z", "bookmakers": [ { "key":"snai", "outcomes":{"HOME":2.10,"DRAW":3.40,"AWAY":3.60}, "last_update":"2026-07-14T10:32:05Z" }, { "key":"bet365", "outcomes":{"HOME":2.05,"DRAW":3.50,"AWAY":3.70}, "last_update":"2026-07-14T10:28:12Z" }, { "key":"leovegas", "outcomes":{"HOME":2.08,"AWAY":3.55}, "suspended":["DRAW"], "last_update":"2026-07-14T10:31:44Z" } ]}]}
Three moves to integrate the odds
Create a key
Sign up and generate an odss_live key in a minute. The Free plan is forever, no card required.
Call /odds
One endpoint, all cross-book odds by event and market. Filter by sport, league, market and bookmaker.
Integrate and sync
Stable event_id to recognize events, last_update for freshness, offset to read the whole schedule.
The moat: Italian books.
The ADM bookmakers global APIs don't have
Dozens of ADM-licensed Italian operators, plus international reference books and exchanges. The list active right now is at /api/v1/bookmakers, with an API key: no brand wall to take on trust. country, playable_it and is_exchange are response fields, not query parameters — the endpoint accepts none.
Stable schema
A public, versioned JSON format: the engine's internal shape never leaks into your code.
Reliable integration
Deterministic event_id for the whole prematch window, ISO last_update per book, offset pagination: no-surprise syncs.
Honest freshness
Italian books run on a continuous cycle and the real freshness is measured and public, per book, on the status page (p50 and p95 over the last 24 hours). International books that come from a third-party data provider under a contractual licence have their cadence measured there too, not promised. Every quote declares its last_update: no stale numbers dressed up as live.
The honest comparison.
Where we stand vs global APIs and the market average. No names, no promises.
Comparison against the average of the global/enterprise providers reviewed (Jul 2026). Our side is verifiable from the public endpoints and the docs.
Bookmakers & leagues
Live numbers, counted from the feed right now — not a catalog inventory.
—
bookmakers with odds on the board now
—
sports priced right now
—
league names on the board, as the books write them
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.
Leagues on the board right now
Live data from the feed (public endpoint /api/v1/leagues): for each sport, the leagues currently priced. Names are the values for the league filter.
Loading the schedule…
The board follows the season: club football is naturally thinner in summer.
Documentation
Base URL https://odss-api.com. Authenticate with the x-api-key: odss_live_… header or Authorization: Bearer. JSON responses, European decimal odds.
GET /api/v1/mock/odds no key needed
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). 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 and /api/v1/status.
First call in 3 steps
Create a key
Sign up and generate a key in the dashboard. The Free plan is forever.
List sports
Verify the key with a call to /sports.
Get odds
Call /odds with sport and market: you get cross-book odds.
GET /api/v1/odds example
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: [...] }
GET /api/v1/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.
Parameters
| Parameter | Description |
|---|---|
| sport | Exact sport filter (e.g. calcio, tennis, basket). Values: see /api/v1/sports. |
| market | Canonical market: 1x2, ou, btts, dc, ah (Asian handicap), eh (European handicap), moneyline… |
| bookmakers | CSV list of book keys (e.g. snai,bet365). Values: see /api/v1/bookmakers. |
| league | Case-insensitive substring filter (e.g. serie a). Values: see /api/v1/leagues. |
| event_id | A single event, by exact id. |
| state | 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 | Records per page. Default 500, max 5000. |
| offset | Start index for pagination. Default 0. |
| content | 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 | Case-insensitive substring of the player name; implies content=player. |
| commence_from · commence_to | 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 | 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 | 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 | 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 | 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 | 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=. |
Response fields
| 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's source does not publish the state; [] means it was read 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 sources 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). |
Pagination
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
GET /api/v1/sports
The sports currently available in the feed (the values for the sport parameter).
GET /api/v1/bookmakers
The bookmakers active in the feed, with 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 (commission on winnings) and source (direct when we read the book ourselves, partner_feed when its prices reach us through a third-party data provider).
GET /api/v1/leagues public, no key
The current schedule, aggregated: for each sport the priced leagues, with event and bookmaker counts. Useful to discover values for the league filter.
GET /api/v1/stream Pro and Enterprise
Server-Sent Events: a hello event on connect, then odds events with the CHANGED records (same schema as /odds, each with its own state) and the removed ones, within ~10 seconds of the engine detecting them — no polling. Optional sport and state (prematch|live|all) parameters; initial snapshot via GET /odds?state=…. Data cadence stays the engine's (per-book freshness measured on the status page, including the books that come from a third-party data provider under a contractual licence): the stream removes polling latency, last_update is authoritative. Max 3 concurrent streams; the connection counts as 1 quota request.
GET /api/v1/odds/history
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_..."
GET /api/v1/middles
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.
curl "https://odss-api.com/api/v1/middles?sport=calcio&safe=true" \ -H "x-api-key: odss_live_..."
GET /api/v1/coverage public, no key
How many bookmaker brands have a valid price in the current feed, per region (Italy, USA, UK, world). It does not match the number of entries in /api/v1/bookmakers: that endpoint lists the queryable keys, where dedicated market lanes (e.g. <brand>-corners) count on their own, while here they are folded under the brand.
GET /api/v1/coverage/matrix public, no key
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).
POST /api/mcp MCP
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}}}'
Webhook Enterprise
Register an https URL from the dashboard or via API — POST /api/account/webhooks with your account session (body {url, sport?, state?}; the secret is shown once, max 20 per account) — and receive odds changes via POST: same {changed, removed} payload as the stream. Each delivery is signed: X-Odss-Signature: t=<unix>,v1=<hex> = HMAC-SHA256 of «{timestamp}.{body}» with the webhook secret (verify it, reject old timestamps). https only, redirects not followed; endpoints that repeatedly fail auto-disable. Respond 2xx. One delivery can arrive as several consecutive POSTs, each signed on its own and numbered by the X-Odss-Page: page/total header: every body carries page, pages, changed_total and removed_total, so the first page already tells you how much is coming. Don't rate-limit the endpoint: a rejected page stops the ones after it.
Limits, headers and errors
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. |
Parameters: no silent failures
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"
}
Machine-readable · contract & agents
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.
| Field | 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). |
Usage license
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, from the site. Redistributing the API or the raw data stays outside every plan: for that, write to [email protected].
The right plan for your volume.
Same data coverage on every plan; SSE streaming from Pro, webhooks with Enterprise. Start free, cancel anytime.
Free
To explore the API. Forever, no card.
- 500 requests / month
- 30 requests / min
- Full coverage: every sport and every book in the feed
Hobby
For personal projects and small bots.
- 10,000 requests / month
- 60 requests / min
- Full coverage: every sport and every book in the feed
Starter
For bots and production integrations.
- 100,000 requests / month
- 120 requests / min
- Full coverage: every sport and every book in the feed
Pro
For platforms and high volumes.
- 500,000 requests / month
- 600 requests / min
- SSE streaming (/api/v1/stream)
- Full coverage: every sport and every book in the feed
- Priority support
Enterprise
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 license →
- SSE streaming included (/api/v1/stream)
- Quota and rate configured on your account
- Automatic activation on payment, cancel anytime
By proceeding you accept the Enterprise & Redistribution License (readable from the link above) in addition to the Terms of service.
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) · Payments via Stripe · one-click cancellation from the portal · the quota covers 30 days from the first request of the period
Three tiers, one engine.
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
Odds API
Normalized odds and data. From the Free plan to Enterprise, public pricing, instant activation.
Tier 2 · self-serve
Surebet API
The surebets already found and computed by the engine, not just the odds, recomputed on every scan cycle: cadence and freshness are measured on the status page. An API plan at €950/month + VAT, resale licence included.
Tier 3 · under contract
Surebet White Label
The complete platform, under your brand, ready to be commercialised. €2,000/month + VAT.
Surebet API
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.
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).
- Event and market
- Bookmakers involved and the odds of each leg
- Arbitrage percentage/margin
- Optimal stakes
- Odds timestamp and freshness
- The identifiers needed for integration
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.
Surebet White Label
For companies that do not want to build the platform themselves: the complete Surebet platform, delivered as a service under your own brand.
- Surebet engine
- Data and updates
- Complete frontend
- Logo, name and colour customisation
- Hosting and infrastructure
- Maintenance and updates
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.
Frequently asked questions.
Can I use the data in a commercial product (SaaS)?
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, bought from the site like any other API plan: €950/month + VAT, with the licence to resell them included in the subscription and the declared-use form required before payment. 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.
How often are odds updated?
Italian bookmakers on a continuous cycle — the REAL freshness, measured per book (p50/p95 over the last 24 hours), is public on the status page, including the international books that come from a third-party data provider under a contractual licence. You don't have to take our word for it: every quote in the response carries its last_update.
Is there a trial of the paid plans?
The Free plan is forever and has the same coverage as the paid plans: it's the best way to verify the data. If you need more volume for an evaluation, write to [email protected].
How is this different from surebett.app?
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.
Is there an API for the odds of ADM-licensed Italian bookmakers?
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.
Can I export odds to a database or Excel?
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.
How much does a sports odds API cost?
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.
Are the odds real-time?
Italian books run on a continuous cycle and freshness is measured, not promised: p50 and p95 over the last 24h are public on the status page, per book. SSE streaming for push; in-play has its own feed.