Example client
A complete client in cURL, Python and JavaScript: quote, call, read the token headers and poll with ETags.
Before you start
You need an API key. Sign up free: your first key is shown once when you finish. Save it as the environment variable ODDSRELAY_KEY, and keep it out of code, query strings and anything that runs in a browser.
The whole flow in three languages
Each sample makes one small call on the standard product: football match odds at one bookmaker in the UK & Ireland. It prices the call first with a free quote, makes it with gzip, reads the token headers, then polls again with the ETag. It backs off on a 429 and stops on a 402. Each is tested against the live API.
#!/usr/bin/env bash # OddsRelay API sample, cURL. Run: ODDSRELAY_KEY=or_live_… bash oddsrelay.sh # # One small filtered call on the standard board, the way every client should make it: # 1. price it first with a free quote=true; # 2. make it with --compressed (gzip; standard refuses a call without it) and read the token headers; # 3. poll it again with If-None-Match, where an unchanged board answers 304, free; # 4. back off on 429 (Retry-After) and stop on 402 (out of tokens). set -euo pipefail BASE="https://api.oddsrelay.io" : "${ODDSRELAY_KEY:?set ODDSRELAY_KEY}" # Football match odds at one bookmaker, UK & Ireland: a small call. URL="$BASE/v2/odds/standard?region=uk&sports=soccer&markets=h2h&bookmakers=ladbrokes" MAX_TOKENS=1000 tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT # call URL [extra header]: writes headers to $tmp/h, body to $tmp/b, prints the status. Retries 429s. call() { local status extra=() [ -n "${2:-}" ] && extra=(-H "$2") for _ in 1 2 3; do status=$(curl --compressed -sS -o "$tmp/b" -D "$tmp/h" -w '%{http_code}' \ -H "Authorization: Bearer $ODDSRELAY_KEY" ${extra[@]+"${extra[@]}"} "$1") if [ "$status" = 429 ]; then # The front door's own 429 has an empty body; both carry Retry-After. wait=$(awk -F': ' 'tolower($1)=="retry-after"{print $2+0}' "$tmp/h") echo "429: waiting ${wait:-1} s" >&2 sleep "${wait:-1}" continue fi if [ "$status" = 402 ]; then echo "402: out of tokens: $(cat "$tmp/b")" >&2 exit 1 fi echo "$status" return done echo "still rate-limited after 3 tries" >&2 exit 1 } header() { awk -F': ' -v k="$(echo "$1" | tr 'A-Z' 'a-z')" 'tolower($1)==k{sub(/\r$/,"",$2); print $2}' "$tmp/h"; } tokens() { echo "cost=$(header X-Tokens-Cost) remaining=$(header X-Tokens-Remaining) reset=$(header X-Tokens-Reset)"; } # 1. Quote: free. [ "$(call "$URL"e=true")" = 200 ] || { echo "quote failed: $(cat "$tmp/b")" >&2; exit 1; } cost=$(sed -E 's/.*"cost": *([0-9]+).*/\1/' "$tmp/b") echo "quote: $cost tokens" [ "$cost" -le "$MAX_TOKENS" ] || { echo "quote $cost is above $MAX_TOKENS: narrow the filters" >&2; exit 1; } # 2. The call. [ "$(call "$URL")" = 200 ] || { echo "call failed: $(head -c 200 "$tmp/b")" >&2; exit 1; } etag=$(header ETag) echo "200: $(tokens)" # 3. Poll with the ETag: 304 if nothing changed (free), else a fresh 200. status=$(call "$URL" "If-None-Match: $etag") case "$status" in 200|304) ;; *) echo "poll answered $status" >&2; exit 1 ;; esac echo "$status on poll: $(tokens)" echo "ok"
What to change first
Swap the product in the path, and the filters in the query, for the data you need. Keep region on every call, keep gzip on, and keep the quote step for any call you haven't priced before.
From the blog
- Historical odds in Python: one event's movesA flat price and a gap in the recording look alike until your code reads the gap list.
- Export betting odds to CSV in PythonPut each back and lay offer on its own row and a spreadsheet can filter by side, venue and depth.
- Efficient odds API polling with ETag and 304sSend the ETag back as If-None-Match and an unchanged reply is an empty 304 that costs no tokens.
Every parameter: API reference.