Sports odds API · for developers and platforms

Italian ADM, international and exchange bookmakers in one API.

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.

  • Free: 500 requests a month, no card
  • Public sandbox, no key
curl "https://odss-api.com/api/v1/mock/odds?sport=calcio&market=1x2&limit=2"

Real feed: /api/v1/odds with the x-api-key header · SDK Python · TypeScript

Sample · synthetic dataResponse · excerptapplication/json
{
  "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.

GET /api/v1/mock/oddsmockbook_* books, same shape as /api/v1/odds
—bookmakers in the feed
—sports on the board
—leagues on the board
—age p50 · 24 h
—age p95 · 24 h
Feed status

Age of the Italian prices you receive, over the last 24 hours. How we measure it

How it works

From key to first price in three steps.

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_…"

Ask for the odds

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"

Read freshness and usage

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

How old a price is when it reaches you.

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.

Age of the Italian prices you receive, last 24 hours
p50
half of the served prices below this age
p95
95 served prices out of 100 below this age

How we measure it

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.

What it is not

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.

Open the feed status

Product

Everything you need to work with odds.

REST to query and streaming to receive, with documentation that machines can read too. Price history is not active yet.

One format for every bookmaker

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.

From the sandbox · Juventus - Inter · 1x2 · synthetic data
bookmakers[].keyHOMEDRAWAWAYlast_update
mockbook_it_12.273.143.0623:59:00Z
mockbook_it_22.803.452.4623:58:23Z
mockbook_it_33.902.42suspended23:57:46Z
mockbook_it_43.162.642.5823:57:09Z
mockbook_it_52.432.423.6623:56:32Z
mockbook_it_62.362.314.4923:55:55Z
mockbook_it_72.113.792.8123:55:18Z
mockbook_uk_11.972.87suspended23:24:41Z
mockbook_sharp4.022.173.2223:54:04Z
mockbook_exchange2.893.302.4423: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.

40+ ADM-licensed Italian bookmakers in one feed

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.

keycountryplayable_itis_exchangesource

Response fields of /api/v1/bookmakers: the endpoint takes no parameters. On /api/v1/odds, country and playable_it also work as filters.

Pro and Enterprise

SSE streaming

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

Price history · not active yet

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.

openingclosing

Verifiable coverage, no key

/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

Enterprise

Signed webhooks

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>, …}

MCP server for AI agents

An agent discovers the tools with no key and no quota used; the key is only needed to call them.

JSON-RPC methodKey and quota
initializeno
tools/listno
tools/callyes

Comparison

The comparison, without names.

Where we stand vs global APIs and the market average. No names, no promises.

Featureodss-apiMarket average
Italian ADM booksYes, 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 payPer 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-bookDeterministic. One event_id shared by all markets of a fixture: native comparison, no heuristic matching.Often heuristic. Proprietary odds or non-deterministic matching.
FreshnessPer 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-readyYes, 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 & onboardingPublic 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 keysBy sport or league. E.g. a «soccer only» key, with no key-count limit.Not documented.

Comparison against the average of the global/enterprise providers reviewed (Jul 2026). Our side is verifiable from the public endpoints and the docs.

Coverage

What's in the feed, counted right now.

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.

Coverage by book and market

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.

The board follows the season: club football is naturally thinner in summer.

Pricing

Same coverage on every plan. You only pay for volume.

SSE streaming comes with Pro, webhooks with Enterprise. Start free and cancel anytime.

On every plan, Free included:

  • Every sport and every book in the feed
  • Player props
  • Delta sync with changed_since
  • Internal use (models, bots, analysis)

Free

To explore the API. Forever, no card.

0 € forever, no card

500 requests/month · 30/min

Example: writing and testing the integration, sandbox included.

Start free

Hobby

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.

Choose Hobby

Starter

Recommended

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.

Choose Starter

Pro

For platforms and high volumes.

159 € per month + VAT

500,000 requests/month · 600/min

  • SSE streaming (/api/v1/stream)
  • Priority support

Example: ten leagues including live, read every 90 seconds (about 288,000 requests a month), or the stream.

Choose Pro

  • Payment via Stripe You confirm the total in the summary before paying
  • Italian e-invoicing Via SDI or PEC, for holders of an Italian VAT number
  • One-click cancellation From your account area, in the payment portal
  • PRAXIS 3 S.r.l. VAT and tax code 01434870661

Enterprise

For platforms, companies and resellers: configure volume, rate and license — the price updates by itself and you activate at checkout, no negotiation.

  • SSE streaming included (/api/v1/stream)
  • Quota and rate configured on your account
  • Automatic activation on payment, cancel anytime
1 milione
+30 € per ogni milione oltre il primo.
600
+20 € per ogni 300 richieste al minuto oltre le 600.
+120 €

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

With this configuration

199 € per month + VAT

From €199 to €749 a month, excl. VAT.

Activate Enterprise

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). The quota covers 30 days from the first request of the period.

Commercial solutions

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.

See pricing

Tier 2 · API plan

Surebet API

Resale licence included

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.

  • 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.

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

Surebet White Label

Platform as a service

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.

  • 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.

More questions? Write to [email protected] or open a ticket: a person replies, by email.

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?

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.

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, 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.

How often are odds updated?

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.

Is there a trial of the paid plans?

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].

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.

For developers

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) · /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.

First call in 3 steps

  1. Create a key Sign up and generate a key in the dashboard. The Free plan is forever.
  2. List sports Verify the key with a call to /sports.
  3. 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_..."

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

ParameterDescription
sport stringExact sport filter (e.g. calcio, tennis, basket). Values: see /api/v1/sports.
market stringCanonical market: 1x2, ou, btts, dc, ah (Asian handicap), eh (European handicap), moneyline…
bookmakers stringCSV list of book keys (e.g. snai,bet365). Values: see /api/v1/bookmakers.
league stringCase-insensitive substring filter (e.g. serie a). Values: see /api/v1/leagues.
event_id stringA single event, by exact id.
state prematch | live | allprematch (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 integerRecords per page. Default 500, max 5000.
offset integerStart index for pagination. Default 0.
content match | player | allmatch = 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 stringCase-insensitive substring of the player name; implies content=player.
commence_from · commence_to date-timeWindow 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 booleantrue = keep only the bookmakers playable from Italy (ADM licence) inside each record; false = only the others. Records left with no bookmaker are dropped.
country stringCSV of licensing countries (ISO 3166-1 alpha-2, or world): only those bookmakers stay inside each record, e.g. IT or GB,SE.
after stringSTABLE 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 | compactcompact = 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 stringDELTA 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.

Response fields

FieldDescription
countTotal records matching the filters (across all pages).
offset · returnedEcho of the requested offset and the records actually returned in this page.
odds[].event_idDeterministic event id: identical for every market of the same fixture and stable for the whole prematch window.
odds[].eventNormalized event name (Home - Away).
odds[].sport · leagueSport and league/competition of the event.
odds[].home_team · away_teamThe two teams, when available.
odds[].commence_timeEvent start, ISO 8601 UTC.
odds[].market · line · scope · periodCanonical market, optional line (e.g. 2.5), scope (e.g. team) and period (e.g. 1st half).
odds[].state · score · minutestate = 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[].keyBookmaker key, with country, playable_it and is_exchange.
…bookmakers[].outcomesOutcome → decimal odds map (e.g. HOME: 2.10).
…bookmakers[].suspendedOutcomes 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_updateISO timestamp of that book's last successful odds update.
…bookmakers[].familyPlatform 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_idOnly 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, each with these fields.

FieldDescription
countryISO 3166-1 alpha-2 code of the licensing country (IT, GB, US, SE…), or world when none is attributed.
playable_ittrue 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_exchangeExchange, with commission on winnings.
feed_keysThe 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_fromOnly 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).
sourceThe price list's channel type: direct | partner_feed.

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: 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.

ItemDescription
helloOn connect.
oddsThe CHANGED records (same schema as /odds, each with its own state) and the removed ones.
sport · stateOptional parameters (state: prematch | live | all). Initial snapshot via GET /odds?state=….
CadenceThat 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).
LimitsMax 3 concurrent streams; the connection counts as 1 quota request.

GET /api/v1/odds/history

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_..."

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.

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_..."

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 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.

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).

GET /api/v1/pulse public, no key

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).

FieldDescription
prematch_it_atThe instant the snapshot containing the current Italian lane was published: never newer than the pages being served.
beatsThe latest arrivals of a new Italian lane in the served snapshot, up to 30, collected since the server started (beats_since).
freshness_24hFor 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.

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 and receive odds changes via POST, with the same {changed, removed} payload as the stream.

ItemDescription
POST /api/account/webhooksFrom the dashboard or via API, with your account session. Body {url, sport?, state?}; the secret is shown once, max 20 per account.
X-Odss-Signaturet=<unix>,v1=<hex> = HMAC-SHA256 of «{timestamp}.{body}» with the webhook secret. Each delivery is signed: verify it, reject old timestamps.
X-Odss-Pagepage/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.
RulesRespond 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.

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.

StatusDescription
400Invalid 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.
401Missing, invalid or revoked key.
403Out 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).
429Burst: per-minute requests of your plan exceeded. Retry-After: 60 header.
429Monthly quota exhausted: upgrade or wait for the window.
502Feed 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"
}

Resources for machines and 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.

ResourceDescription
/openapi.jsonOpenAPI 3.1 contract (endpoints, parameters, schemas, x-api-key auth).
/llms.txtMarkdown guide for LLMs (llmstxt.org): summary and links to resources.
/postman_collection.jsonPostman collection: import it and set the x-api-key variable to try the endpoints right away.
/sdk/odss_api.pyOfficial Python SDK (single-file, zero dependencies): typed client with automatic pagination and live support.
/sdk/odss-api.tsOfficial TypeScript SDK (single-file, zero dependencies): types, async iterator and an SSE stream helper.
/.well-known/api-catalogAPI 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. Redistributing the API or the raw data stays outside every plan: for that, write to [email protected].

Try the feed now. The sandbox doesn't even ask for a key.

When you're ready, the Free key takes a minute. No card.

Get a free key
curl https://odss-api.com/api/v1/mock/odds
odss-api Back to the site
Access
Forgot your password?
Contact the team
Write to us: we open a ticket and reply by email. No bots.
If it is about an API request, paste the URL without the key and the time you made it.
Before payment
Who are you buying for?