How to switch odds data providers without downtime
OddsRelay · · Updated · 5 min read
Migrating between odds feeds safely means running both side by side before you commit. Map the new feed into the shape you already consume, shadow it against your current provider on your own markets, and cut over only when the diff says it holds up.
Users see no downtime this way, because the old feed keeps serving until the new one has earned the traffic. If you are still shortlisting, how to choose an odds data provider comes first, and testing a feed before you buy covers the acceptance run. A migration is that acceptance run with your existing feed as the benchmark.
Step 1: map the new feed's fields to yours
Write the mapping down before you write code. Your ingestion layer reads a fixed set of keys today, and the migration is easy or painful depending on how cleanly the new feed lines up with them. Put every provider-specific name in one adapter and let no other code know who supplies the data.
On an OddsRelay matched board, each event carries its teams and kick-off, then markets, then outcomes. Each outcome has back offers (a bookmaker's price) and lay offers (an exchange's price with available, the money on offer). The response wraps the rows in a meta envelope with the board time. Here is a trimmed example from GET /v2/odds/standard:
{
"meta": {
"region": "uk",
"odds_format": "decimal",
"processed_at": "2026-09-17T02:20:57.271Z",
"last_seen": { "william_hill": { "soccer": "2026-09-17T02: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": "Arsenal",
"away_team": "Chelsea",
"markets": [{
"key": "h2h",
"outcomes": [{
"name": "Arsenal",
"back": [{ "bookmaker": "william_hill", "price": 2.9, "link": null }],
"lay": [{ "exchange": "betfair_exchange", "price": 3.0, "available": 175, "link": null }]
}]
}]
}]
}| Field to map | What to check when you map it |
|---|---|
event_id | Treat it as opaque. It is stable within the feed's own mapping, but do not make it the durable key in your history tables. |
sport_key, commence_time, home_team, away_team | These are what you join the two feeds on during the parallel run. Team names differ between providers, so normalise them first. |
back[].bookmaker, lay[].exchange | Snake_case venue slugs from GET /v2/bookmakers. Map each one to your own venue list once. |
lay[].available | The money on offer at that lay price. If your old feed had no liquidity figure, decide how your product will show it. |
meta.processed_at, meta.last_seen | The board time and the last read per venue and sport. These replace whatever freshness field your old feed sent. |
The mapping for a matched board. The full field list is in the API reference.
Two differences catch teams moving from a ratings-style feed. First, each back price is paired against exchange lay and liquidity-gated, and the rating and qualifying loss are simple arithmetic on the pair, at your own commission rate. The oddsmatcher widget shows them. If your old provider sent a rating field, your adapter now computes it, for example const rating = (outcome.back[0].price / outcome.lay[0].price) * 100. Second, event ids from two providers never line up, so keep your own event key and a crosswalk table that records which provider id belongs to it.
Step 2: run the new feed in parallel
Point the new feed at the markets you actually serve and write its output to a shadow table your users never see.
- Same events, both feeds. Request the sports and bookmakers you sell, filtered the way production will be. A feed can look complete on football and thin on the markets you depend on.
- Polling. OddsRelay is pull only, with no webhook or stream. If your old provider pushed updates, the adapter needs a poller. Send each reply's
ETagback asIf-None-Match: an unchanged board returns a304and costs no tokens. - Token cost. A call's token price comes from the request: the board and its filters. Reply size does not change it. Add
?quote=trueto price a call without spending. The filters and tokens guide explains it. - Peak-hour window. A quiet afternoon tells you little. Freshness and completeness are hardest to hold when the markets are busiest.
A few integration details bite on day one. The key goes in Authorization: Bearer <key> or x-api-key. Send region=uk on every call. Make sure your HTTP client sends Accept-Encoding: gzip: without it replies come back as much larger plain bodies, and a client that explicitly refuses gzip gets a 400 from the standard, dutching and raw boards. The quickstart walks through a first call.
Step 3: diff coverage and freshness on your real markets
This is the decision gate. Join the shadow rows to your live rows on your own event key and answer one question: does the new feed match or beat what you have today, on the books and markets you sell? Evaluating coverage goes deeper. For a migration, these are the checks that matter.
- Book breadth. The new feed should carry every book you rely on, and ideally close a gap. OddsRelay lists 140+ bookmakers, 60+ of them live in the UK & Ireland, bet365 included on every plan. Check what is live, per product, venue and sport, on the coverage page or with a keyless
GET /v2/coveragebefore you ask for a key. - The lay side. Each back price should sit beside an exchange lay price and the money available at it. OddsRelay's lay side comes from Betfair, Smarkets, Matchbook and BETDAQ.
- Freshness. For each venue and sport, now minus
meta.last_seenis an upper bound on a price's age. On the raw board, each market'slast_updateis when that market last changed, so a quiet market can carry an old time and still be current. Compare like with like on the same events. The freshness guide defines each field, and every product on sale meets a maximum update time of 10–20 s. - Completeness across the run. Count the selections that dropped out when a source hiccupped. Each market should stay whole for the whole window.
- In-play. The feed is built for pre-match data with no in-play product. If your old provider supplies in-play prices, that part of your product does not move across.
Step 4: cut over behind a flag
With the adapter written and the parallel run green, the cutover is a config change.
- Point production ingestion at the new feed through the adapter from step one.
- Keep the old provider warm briefly, so rollback is a flag flip rather than a re-integration. A clean parallel run can still miss a case that only shows at peak.
- Retire the old feed when that window closes, and update your runbook and your crosswalk table.
Start with what is live
When the books you sell are live, request access for a key to shadow your current feed, and keep the API reference open while you write the adapter.