How to test an odds feed before you buy
OddsRelay · · Updated · 5 min read
Test an odds feed by running the calls your product will make: coverage against your own list, a priced first call, a freshness check, a 304 and a deliberate error. Treat everything in a vendor's deck as a claim until you have.
Can you try the feed before you pay?
Partly. Two parts of the feed answer without a key, and for the rest you request access. We review each request and reply by email. Plans differ only in their monthly tokens, and pricing lists them.
Read the contract before the key arrives
The OpenAPI 3.1 contract is public at /v2/openapi.json. It carries every route, parameter and error code, with example responses. Read it for the field names you will depend on and for how refusals are shaped.
# The contract curl --compressed -s https://api.oddsrelay.io/v2/openapi.json -o openapi.json # Live status per product, venue and sport in the UK & Ireland curl --compressed -s "https://api.oddsrelay.io/v2/coverage?region=uk"
Check coverage against your own list
Write down the bookmakers, exchanges and sports your product needs before you look at anyone's coverage page. A feed with a large book count is no use if it misses the three you need.
OddsRelay covers 140+ bookmakers, 60+ of them live in the UK & Ireland, bet365 included, with lay prices from Betfair, Smarkets, Matchbook and BETDAQ. The keyless coverage call shows each venue and sport, per product, as live, or interrupted (an interruption in the last 24 hours, or no rows now). The coverage page reads the same data. With a key, the same route adds event counts and each venue's last read time, which tells you whether a book that is listed actually has prices for your sports today. Evaluating coverage has the fuller method.
What should your first call look like?
Small, and priced before it runs. Add quote=true to any data request and it returns the call's token price instead of the board, free. A price comes from the board and the filters you send, never from the size of the reply, so on the standard board a narrow call with named sports and bookmakers is the cheap way to look. The quickstart has the whole flow in three languages.
# 1. Price it (free) curl --compressed -s -H "Authorization: Bearer $ODDSRELAY_KEY" \ "https://api.oddsrelay.io/v2/odds/standard?region=uk&sports=soccer&bookmakers=bet365"e=true" # 2. Make it, keeping the headers curl --compressed -s -D headers.txt -H "Authorization: Bearer $ODDSRELAY_KEY" \ "https://api.oddsrelay.io/v2/odds/standard?region=uk&sports=soccer&bookmakers=bet365"
Read the first reply by hand before you write integration code. Check that your books appear under back, that your markets appear under markets, and that the event names and kick-off times match what the bookmaker shows.
{
"meta": {
"feed_type": "standard", "region": "uk", "odds_format": "decimal",
"processed_at": "2026-09-17T14:20:57.271Z",
"last_seen": { "bet365": { "soccer": "2026-09-17T14:20:55Z" } },
"count": 1,
"version": "v2",
"next_cursor": null
},
"data": [{
"event_id": "or_evt_917dd44bce05",
"sport_key": "soccer_epl",
"sport_title": "Premier League",
"commence_time": "2026-09-20T14:00:00Z",
"home_team": "Brentford",
"away_team": "Everton",
"markets": [{
"key": "h2h",
"outcomes": [{
"name": "Brentford",
"back": [{ "bookmaker": "bet365", "price": 3.4, "link": null }],
"lay": [{ "exchange": "betfair_exchange", "price": 3.5, "available": 1620, "link": null }]
}]
}]
}]
}Read the pair, then do the arithmetic
A matched board earns its name in the lay array. Each back offer is paired against exchange lay and liquidity-gated, and available is the money on offer at that lay price. The rating and qualifying loss are simple arithmetic on the pair, and the oddsmatcher widget shows them. Do that arithmetic yourself, at your own commission rate, on a handful of rows.
const back = 3.4, lay = 3.5, commission = 0.02; const rating = (back / lay) * 100; // 97.14 const layStake = (back * 10) / (lay - commission); // for a 10-unit back stake
How do you check freshness?
With the timestamps in the reply, and a browser. Every response carries the board's time in meta.processed_at and the X-Processed-At header. On a matched board, meta.last_seen gives each venue and sport's last read, so now minus that time is an upper bound on a price's age. Take a few rows and compare them with the bookmaker and exchange sites at the same moment. They will not match to the penny on a moving market, but they should be close.
Every product on sale meets a maximum update time of 10–20 s. Run the comparison on fixtures that have not kicked off. The feed has no in-play board, so there is nothing to check once play starts. The freshness guide covers both fields in detail.
Prove the 304 before you set a polling rate
The feed is pull only, so your running cost depends on how it answers a repeat call. Every data reply carries an ETag. Send it back as If-None-Match and an unchanged board comes back as 304 Not Modified with an empty body, which costs no tokens. It still counts toward the rate limit, so poll at the pace your product needs rather than as fast as you can. Conditional requests has the details.
ETAG=$(grep -i '^etag:' headers.txt | cut -d' ' -f2- | tr -d '\r') curl --compressed -s -i -H "Authorization: Bearer $ODDSRELAY_KEY" \ -H "If-None-Match: $ETAG" \ "https://api.oddsrelay.io/v2/odds/standard?region=uk&sports=soccer&bookmakers=bet365" | head -n 1 # HTTP/2 304 (nothing changed: keep the rows you have)
Break it on purpose
How a feed fails tells you more than the happy path. Send a bookmaker key that does not exist: the reply should be a structured error that lists the valid values. Here it is a 400 with the code unknown_bookmaker. The API's errors are JSON with a code and a request_id you can quote to support. A 429 carries Retry-After, sometimes with an empty body, so handle both. A call your balance cannot cover is a 402 that serves and spends nothing.
Then run the exact call your product will make, at roughly the rate it will make it, for a few minutes. You are checking that your client handles the retry signals and the 304s, not trying to knock the service over.
What good looks like, and the red flags
| Signal | Good | Red flag |
|---|---|---|
| Schema | Public contract with example responses | Field list only after a sales call |
| Coverage | Live status per venue and sport | A book count in a PDF |
| Matched data | Back paired with exchange lay and liquidity | Back prices only, matching "coming soon" |
| Freshness | Timestamps in every reply | Speed promises with nothing to check them against |
| Cost | Free quotes and free 304s | Every repeat call billed in full |
Where to start
Open the coverage page and hold it against your list of books and sports. If it covers them, request access and run your first quoted call with the quickstart.