Skip to content
OddsRelay

The anatomy of an odds API response

What every field in a good odds API response is for, walked one at a time: the event/market/selection keys, the back and lay blocks, rating and qualifying_loss, and the envelope around them.

James6 min read

A good odds API response is a list of opportunities. Each opportunity names the event, the market and the selection, then carries the back price and, if the row is matched, the lay price, a rating and a qualifying_loss. That list sits inside an envelope with an opportunity_count and a processed_at timestamp. This walks every field, one at a time, and explains why the matched part is the bit a raw API cannot hand you.

What does a single opportunity look like?

A single opportunity is one row that ties a bookmaker price to an exchange price for the same selection. It is the smallest unit your oddsmatcher or scanner can render. Here is one matched row inside its envelope (illustrative shape, not live data):

One matched opportunity in its envelope · illustrative
{
  "opportunities": [
    {
      "event": "Arsenal vs Chelsea",
      "market": "match_odds",
      "selection": "Arsenal",
      "back": { "bookmaker": "bet365", "odds": 2.10 },
      "lay":  { "exchange": "betfair", "odds": 2.14, "liquidity": 1840 },
      "rating": 98.1,
      "qualifying_loss": -0.12
    }
    // ... more opportunities
  ],
  "opportunity_count": 1,
  "processed_at": "2026-07-01T09:14:02Z"
}

Everything below is a field in that object. Read it once and the rest of the full API reference reads like a checklist. For where these fields come from upstream, see how odds data is structured.

What is each field for?

The first three fields locate the price. The next two carry it. The last two grade it.

event, market, selection

  • event is the human-readable fixture, for example "Arsenal vs Chelsea". Use it for display; match on the keys below, not on this string.
  • market is the market key, for example match_odds or over_under_2_5. It is stable and machine-friendly, so your code groups and filters on it.
  • selection is the runner the price applies to inside that market, for example "Arsenal" or "Over 2.5". Together, event + market + selection identify exactly what is priced.

back { bookmaker, odds }

The back block is the bookmaker side: the price you would bet at a book. back.bookmaker is the book slug, for example bet365, and back.odds is its decimal price, for example 2.10. Coverage here spans 60+ UK books with bet365 included, so the same response shape carries every book you care about rather than a special case per supplier.

lay { exchange, odds, liquidity }

The lay block is the exchange side, present only on matched rows. lay.exchange is one of three exchanges (Betfair, Smarkets, Matchbook), lay.odds is the current lay price for the same selection, and lay.liquidity is the money available at that price. Liquidity matters: a lay price with nothing behind it is a number you cannot actually act on, so the feed reports it rather than leaving you to guess.

rating and qualifying_loss

rating and qualifying_loss are the graded result of pairing back against lay. rating expresses how close the back/lay pair is to a perfect match, on a scale where higher is better, so you can sort and threshold opportunities directly. qualifying_loss is the small expected cost of placing the qualifying bet, expressed as a signed number. The deeper meaning of both, and how to use them, is in qualifying loss and ratings.

What is the envelope around the opportunities?

The envelope is the small wrapper that makes the list cheap to poll. It holds three things: the opportunities array itself, the opportunity_count, and a processed_at timestamp.

  • opportunities is the array of rows above. An empty array is a valid answer: it means nothing currently qualifies for your filter, not an error.
  • opportunity_count is the length of that array, handy for logging, paging logic and a quick sanity check that a filter did what you expected.
  • processed_at is the timestamp the matched board was last computed, in UTC. It tells you how fresh the whole response is in one field, rather than per row.

processed_at reflects our freshness posture honestly: pre-match polling on roughly a few-second cycle. It is the field to watch if you care about staleness, and it is checkable rather than asserted.

How do you poll it cheaply?

You poll with conditional requests so most calls cost almost nothing. Each response carries an ETag. Send it back on the next request as If-None-Match, and when nothing has changed the feed answers 304 Not Modified with no body. You spend a round trip and a header instead of re-downloading the full board.

That pattern pairs naturally with the few-second cycle: poll on your interval, carry the ETag, and only parse a fresh payload when processed_at actually advances. The endpoints and headers are listed in the full API reference. For the broader concept of what sits behind this shape, see what an odds API is.

Why can't a raw API give you the matched part?

A raw odds API gives you the back block and stops there. The lay block, the rating and the qualifying_loss are the output of matching each bookmaker price against a live exchange price for the same selection, with liquidity checked. That step is the product, and it is what a list of raw prices structurally cannot contain.

To produce those three fields yourself you would need an exchange integration, a matcher that pairs selections correctly across differently-worded markets, and a liquidity model, all kept current. The response shape hides that work behind seven readable fields. Build it and you own the pipeline; receive it and your scanner renders on day one.

Field groupRaw odds APIMatched feed
event / market / selectionUsually presentPresent, normalised across books
back { bookmaker, odds }PresentPresent, 60+ UK books incl. bet365
lay { exchange, odds, liquidity }AbsentPaired against Betfair, Smarkets or Matchbook
rating / qualifying_lossAbsentComputed per row

Read the response top to bottom and the division is clear: the first half is what any odds source can describe, and the second half is what has to be computed before you receive it. The matched half is the reason teams stop maintaining their own board and license a feed instead.

The short version

An odds API response is a list of opportunities in a thin envelope: event, market and selection locate the price; back carries the bookmaker odds; lay, rating and qualifying_loss carry the matched result; and opportunity_count plus processed_at wrap it, with ETag/304 keeping polling cheap. OddsRelay returns exactly this shape, and it powers a leading UK matched-betting platform today. The quickest way to read a real one is a free trial key against the live feed.

Fundamentals

Written by

James

Founder, OddsRelay

James is the founder of OddsRelay — the odds-data feed behind matched betting, arbitrage and odds-comparison products: 60+ UK bookmakers with bet365 included, matched against exchange lay prices and delivered as one clean, documented API. He writes here about how that data layer actually behaves — coverage, matching, freshness and the trade-offs — from the side that builds and runs it. The same feed powers a leading UK matched-betting platform today.

Part of the Fundamentals cluster

What is an odds API? A 2026 guide for builders

18+ · Data product for licensed operators. Please gamble responsibly.