Quickstart
Make your first small call, price the big ones first and poll without paying twice.
Also as Markdown.
Before you start
You need a key. Access is by request today, and the key arrives by email. Put it in an environment variable called ODDSRELAY_KEY and never in code, a query string or a browser bundle.
The whole flow in three languages
Each sample makes one small call on the standard board: soccer 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}"
# Soccer 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 board 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.
Every statement here follows the published contract. The API reference has the full detail.