BlogBuying a feed

Switching odds data providers without downtime

To switch odds data providers without downtime, run the new feed beside the old one before you commit: map it into the shape you already consume, shadow it on your own markets, and cut over only when the diff 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:

A standard matched board (trimmed) · example

{
  "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_premier_league",
    "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": "smarkets", "price": 3.0, "available": 175, "link": null }]
      }]
    }]
  }]
}
Field to mapWhat to check when you map it
event_idTreat it as opaque. It never changes once served, so it can key your history tables. An id merged into another is listed in that event's previous_ids and still answers.
commence_time, home_team, away_teamThese are what you join the two feeds on during the parallel run. Team names differ between providers, so normalise them first. You can narrow the join to a sport family, such as the soccer prefix, or to one competition with competition_key, but do not join on sport_key. It follows the league names the venues use, so one competition's matches can carry different ones.
back[].bookmaker, lay[].exchangeSnake_case venue slugs from GET /v2/bookmakers. Map each one to your own venue list once.
lay[].availableHow much can be laid at that price. If your old feed had no liquidity figure, decide how your product will show it.
meta.processed_at, meta.last_seenThe 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 API reference lists every other field.

Two differences catch teams moving from a ratings-style feed. First, the feed sends the back and lay prices and the available money, and no derived figures: the rating and qualifying loss are yours to work out at your own commission rate, as the oddsmatcher widget does. 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. A shadow run spends tokens like any other traffic, so keep it to those markets: what drives the cost of an odds feed shows where they go.

  • 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 ETag back as If-None-Match: an unchanged board returns a 304 and costs no tokens.
  • Token cost. A live-feed call's token price comes from the request: the board and its filters. Reply size does not change it. Add ?quote=true to price a call without spending. The filters and tokens guide explains it, and pricing lists each plan's tokens.
  • 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, with bet365 on every plan for featured football and UK and Irish racing. Check the coverage, per product, venue and sport, on the coverage page, or the live status with a keyless GET /v2/coverage, before 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 and Matchbook.
  • Freshness. For each venue and sport, now minus meta.last_seen is an upper bound on a price's age. On the raw board, each market's last_update is 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 for match odds, and for every market within six hours of kick-off. Further from kick-off, prices in the other markets can be older.
  • 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. Since 2 November 2026 the feed serves in-play prices too, with inPlay on raw and on the standard and dutching boards, polled like the rest. It carries no scores or match clock, and bet365 and William Hill have no in-play prices, so diff your in-play markets book by book before you move them across.

Step 4: cut over behind a flag

With the adapter written and the parallel run green, the cutover is a config change.

  1. Point production ingestion at the new feed through the adapter from step one.
  2. 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.
  3. Retire the old feed when that window closes, and update your runbook and your crosswalk table.

Start with what is live

Shadow your current feed with the new one on the bookmakers you sell, and keep the API reference open while you write the adapter.

Serving your users from the new feed runs on Enterprise, and the shadow run can start on your own Free key.

Related posts

18+ · OddsRelay supplies odds data only and takes no bets. Please gamble responsibly.