Polling an odds feed efficiently with ETag and 304s
How to poll an odds feed without wasting bandwidth or hitting rate limits: poll on the refresh cadence, send If-None-Match for a cheap 304 when nothing has changed, and accept gzip.
James··5 min read
Poll on the feed's refresh cadence, send `If-None-Match` with the last `ETag` you saw, and accept gzip. When nothing has changed you get a cheap 304 Not Modified with no body. When something has changed you get the full payload and a new ETag to carry forward. That keeps your data fresh without wasting bandwidth or tripping rate limits. This post explains why each of those three habits matters for a feed that updates every few seconds.
How often should you poll an odds feed?
Poll at the rate the feed actually refreshes, not faster. OddsRelay does pre-match polling on roughly a few-second cycle, so a fresh request lands every few seconds regardless of how often you ask. Requesting the same market ten times a second buys nothing: nine of those ten calls return data you already have. The refresh cadence is the ceiling on how fresh your copy can be, so match your interval to it and stop there.
Pick your interval to be close to the cadence, then let conditional requests handle the rest. A few-second poll aligned to a few-second feed sees each new price roughly once and pays almost nothing when a price hasn't moved. That is the pattern this whole post is built around.
What is an ETag, and why does it matter here?
An ETag is a short opaque string the server sends in a response header to identify that exact version of the resource. Think of it as a fingerprint for the current state of a market. If the odds behind it are unchanged next time you ask, the ETag is the same. If any price moved, the ETag changes.
You do not parse an ETag or reason about its contents. You store the last one you received per endpoint, and hand it back on your next request. The server compares your value against the current one and tells you whether anything moved. For a feed where most selections are stable between two few-second ticks, that comparison lets the server skip resending a payload you already hold.
How do If-None-Match and 304 Not Modified work?
You send the stored ETag back in an If-None-Match request header. The server reads it and takes one of two paths. If the current version matches what you sent, it replies 304 Not Modified with headers only and no body, so you keep the data you already have. If the version differs, it replies 200 OK with the full JSON and a fresh ETag for you to store. Here is the request-and-304 exchange (illustrative):
Conditional request and a 304 response · illustrative
# Request: hand back the ETag from your last 200 OK
GET /v1/odds/matched?market=match_odds HTTP/1.1
Host: api.oddsrelay.io
Authorization: Bearer or_live_xxx
Accept-Encoding: gzip
If-None-Match: "8f3c1a2e"
# Response when nothing has changed: no body, keep what you have
HTTP/1.1 304 Not Modified
ETag: "8f3c1a2e"
Cache-Control: max-age=3
When the price does move, the same request returns the payload instead: the full row with the bet365 back price, the paired exchange lay, and the computed rating and qualifying_loss, alongside a new ETag value.
The 200 OK you get when a price has moved · illustrative
The saving is real. On a feed that updates every few seconds, most consecutive polls for a given market find nothing changed, so most of your requests come back as small, bodiless 304s. You still poll on cadence, but you only pay for the full payload when there is a new payload to pay for. For the full shape of that payload, see the response envelope.
Should you compress the response?
Yes: send Accept-Encoding: gzip on every request. Odds payloads are repetitive JSON, full of the same field names and similar numbers, which is exactly the shape gzip shrinks well. The server compresses the body, your client decompresses it, and most HTTP libraries handle that transparently once you ask for it. When you combine gzip with conditional requests, the changed payloads you do receive are small and the unchanged ones cost almost nothing.
What are the anti-patterns to avoid?
Three habits waste bandwidth and put you at risk of rate limits, and all three are avoidable:
Hammering the endpoint. Polling far faster than the feed refreshes adds load and request count without adding freshness. On a roughly few-second cadence, a loop that fires several times a second is nine wasted calls out of ten and the fastest way to hit a limit.
Ignoring 304s. If you never send If-None-Match, every poll pulls a full body even when nothing moved. Sending the stored ETag turns most of those into empty 304s.
Not compressing. Skipping Accept-Encoding: gzip means every payload arrives at full size. It is a one-header change for a large, free reduction in transfer.
Rate limits exist to keep the feed healthy for everyone, and conditional, compressed polling is how you stay comfortably inside them. The specific limits and the headers that report your remaining budget are documented: check the rate limits and headers in the docs and read the RateLimit response headers on each call so you can back off before you are throttled rather than after.
A polling loop that behaves
Put the three habits together and the loop is simple. For each market you track:
Wait for your interval, aligned to the feed's roughly few-second cadence.
Send the request with Accept-Encoding: gzip and If-None-Match set to the last ETag for that exact URL.
On 304 Not Modified, do nothing: your stored data is still current.
On 200 OK, replace your stored data, then store the new ETag for next time.
Read the RateLimit headers and slow down if your remaining budget is running low.
That loop stays fresh to within one refresh cycle, sends almost no wasted bytes, and keeps well clear of rate limits. Once the data is in hand, displaying odds data covers how to render the matched back and lay prices cleanly in your product.
The short version
Poll on the cadence, send If-None-Match for cheap 304s, and accept gzip. Those three habits give you a feed that is fresh to within a few seconds while transferring only the data that actually changed. OddsRelay serves 60+ UK books with bet365 included, each matched against three exchanges (Betfair, Smarkets, Matchbook), and it powers a leading UK matched-betting platform today. Grab a free trial key and point your polling loop at the real thing.
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.