### GET /v2/pricing Source: https://oddsrelay.io/docs Returns the token price table. This endpoint is free and does not require a key. ```APIDOC ## GET /v2/pricing ### Description Returns the token price table. ### Method GET ### Endpoint /v2/pricing ### Authentication No key needed. ### Cost Free ``` -------------------------------- ### GET /v2/status Source: https://oddsrelay.io/docs Returns service status per product. This endpoint does not require a key. ```APIDOC ## GET /v2/status ### Description Returns service status per product. ### Method GET ### Endpoint /v2/status ### Authentication No key needed. ### Cost Free ``` -------------------------------- ### GET /v2/usage Source: https://oddsrelay.io/docs Returns your plan, token balance and rate limits. This endpoint is free and does not require a key. ```APIDOC ## GET /v2/usage ### Description Returns your plan, token balance and rate limits. ### Method GET ### Endpoint /v2/usage ### Authentication No key needed. ### Cost Free ``` -------------------------------- ### GET /v2/pricing Source: https://oddsrelay.io/docs Returns the price table for API calls. This endpoint is mentioned as the way to get the price table. ```APIDOC ## GET /v2/pricing ### Description Returns the price table for API calls. This endpoint is mentioned as the way to get the price table. ### Method GET ### Endpoint /v2/pricing ### Response #### Success Response (200) Returns the price table. #### Response Example (No example provided in source.) ``` -------------------------------- ### GET /v2/openapi.json Source: https://oddsrelay.io/docs Returns the OpenAPI 3.1 contract. This endpoint does not require a key. ```APIDOC ## GET /v2/openapi.json ### Description Returns the OpenAPI 3.1 contract. ### Method GET ### Endpoint /v2/openapi.json ### Authentication No key needed. ### Cost Free ``` -------------------------------- ### GET /v2/usage Source: https://oddsrelay.io/docs Returns usage information for the current account. This endpoint is mentioned as a free discovery endpoint. ```APIDOC ## GET /v2/usage ### Description Returns usage information for the current account. This endpoint is mentioned as a free discovery endpoint. ### Method GET ### Endpoint /v2/usage ### Response #### Success Response (200) Returns usage information. #### Response Example (No example provided in source.) ``` -------------------------------- ### GET /healthz Source: https://oddsrelay.io/docs Returns 200 when the API is up. This endpoint does not require a key. ```APIDOC ## GET /healthz ### Description Returns 200 when the API is up. ### Method GET ### Endpoint /healthz ### Authentication No key needed. ### Cost Free ``` -------------------------------- ### GET /v2/regions Source: https://oddsrelay.io/docs Lists the regions your key covers. This endpoint is free and does not require a key. ```APIDOC ## GET /v2/regions ### Description Lists the regions your key covers. ### Method GET ### Endpoint /v2/regions ### Authentication No key needed. ### Cost Free ``` -------------------------------- ### GET /v2/usage Source: https://oddsrelay.io/docs Shows your current rate limits and usage. This endpoint is used to check the limits for your account. ```APIDOC ## GET /v2/usage ### Description Shows your current rate limits and usage. This endpoint is used to check the limits for your account. ### Method GET ### Endpoint /v2/usage ### Response #### Success Response (200) - **X-RateLimit-Limit** (string) - Your limit for the window closest to running out: 10 seconds or 1 minute. - **X-RateLimit-Remaining** (string) - Requests left in that window. - **X-RateLimit-Reset** (string) - Seconds until that window resets. #### Response Example (No example provided in source.) ``` -------------------------------- ### GET /v2/odds/{type} Source: https://oddsrelay.io/docs Returns matched odds for one product. Requires authentication unless the endpoint is public. ```APIDOC ## GET /v2/odds/{type} ### Description Returns matched odds for one product. ### Method GET ### Endpoint /v2/odds/{type} ### Parameters #### Path Parameters - **type** (string) - Required - The product type for which to get odds. ### Authentication Requires a key sent in the Authorization header as Bearer or in x-api-key. Do not put the key in the query string. ### Cost Tokens ``` -------------------------------- ### GET /v2/health Source: https://oddsrelay.io/docs Returns how recently each product was updated. This endpoint is free and does not require a key. ```APIDOC ## GET /v2/health ### Description Returns how recently each product was updated. ### Method GET ### Endpoint /v2/health ### Authentication No key needed. ### Cost Free ``` -------------------------------- ### GET /v2/sports, /v2/events, /v2/bookmakers, /v2/regions Source: https://oddsrelay.io/docs Free discovery endpoints that list the values the filters accept. ```APIDOC ## GET /v2/sports, /v2/events, /v2/bookmakers, /v2/regions ### Description Free discovery endpoints that list the values the filters accept. ### Method GET ### Endpoint /v2/sports /v2/events /v2/bookmakers /v2/regions ### Response #### Success Response (200) - Returns lists of accepted filter values. ``` -------------------------------- ### GET /v2/usage Source: https://oddsrelay.io/docs/reference Retrieves the account's current scope and scope version. The response includes the scope and scope_version fields, which are null when the account has no scope. ```APIDOC ## [GET] /v2/usage ### Description Retrieves the account's current scope and scope version. The response includes the scope and scope_version fields, which are null when the account has no scope. ### Method GET ### Endpoint /v2/usage ### Parameters None ### Response #### Success Response (200) - **scope** (object) - The account's scope, or null if none. - **scope_version** (string) - The version of the scope, or null if none. #### Response Example { "scope": null, "scope_version": null } ``` -------------------------------- ### Consent-gated load with dynamic script injection Source: https://oddsrelay.io/docs/widgets/consent Load the widget only after consent is granted. The first block is a placeholder that is blocked until your consent callback runs; the second block is the consent-granted callback that dynamically creates the script element. Widgets already on the page mount when the bundle loads; call window.OddsRelay.scan() if you add more later. Visitors who decline see an empty space; if the widget is the page's content, categorise it as functional instead. ```html ``` -------------------------------- ### GET /v2/odds/event/{id} Source: https://oddsrelay.io/docs Retrieves odds for a single event by its ID. This endpoint is mentioned as the way to get odds for one event. ```APIDOC ## GET /v2/odds/event/{id} ### Description Retrieves odds for a single event by its ID. This endpoint is mentioned as the way to get odds for one event. ### Method GET ### Endpoint /v2/odds/event/{id} ### Parameters #### Path Parameters - **id** (string) - Required - The event ID. ### Response #### Success Response (200) Returns the odds for the specified event. #### Response Example (No example provided in source.) ``` -------------------------------- ### GET /v2/bookmakers Source: https://oddsrelay.io/docs Returns bookmakers and exchanges. This endpoint is free and does not require a key. ```APIDOC ## GET /v2/bookmakers ### Description Returns bookmakers and exchanges. ### Method GET ### Endpoint /v2/bookmakers ### Authentication No key needed. ### Cost Free ``` -------------------------------- ### Make first API call for football match odds Source: https://oddsrelay.io/docs Sends a GET request to the standard odds endpoint for soccer h2h markets in the UK region. Uses the ODDSRELAY_KEY environment variable for authentication and --compressed for gzip support. This call costs 1,107 tokens. ```bash # --compressed asks for gzip and decompresses it for you. BASE=https://api.oddsrelay.io curl --compressed -sS -D - \ -H "Authorization: Bearer $ODDSRELAY_KEY" \ "$BASE/v2/odds/standard?region=uk&sports=soccer&markets=h2h" ``` -------------------------------- ### AI Assistant Integration Prompt for OddsRelay API Source: https://oddsrelay.io/docs Copy this prompt into a coding assistant and replace the last line with the desired build. It covers keys, requests, tokens, caching, and all error codes. Tools that read docs directly can use llms.txt and llms-full.txt. ```markdown You are integrating the OddsRelay odds API (version 2026-07-08). Follow this contract exactly. BASE URL https://api.oddsrelay.io (every route is GET; the full contract is https://api.oddsrelay.io/v2/openapi.json) KEYS Send the key as Authorization: Bearer or x-api-key: , never in a query string. Server keys start or_live_. Read the key from an environment variable; never ship it to a browser. THE CALL GET /v2/odds/{type}?region=uk products: standard, 2up, dutching, each-way, extra-place, bog GET /v2/odds/raw?region=uk every bookmaker's prices, paged (meta.next_cursor; a cursor expires after 300 s) On data routes region is one lowercase code; missing means uk (400 region_required if your key lacks uk). Discovery routes like /v2/bookmakers take no region. Any parameter a route doesn't read is 400. Filters on the matched products: sports (<= 20), bookmakers (<= 40), exchanges (<= 4), markets (<= 20), commenceTimeFrom / commenceTimeTo. Discovery lists the values: GET /v2/sports, /v2/bookmakers. TOKENS Every data call costs tokens, priced from the request (product and filters), never the reply's size. Add quote=true to any data request to get {cost, remaining, rate_card} for free, before a big call. Read X-Tokens-Cost on replies to a valid key; X-Tokens-Remaining and X-Tokens-Reset come when the balance is known, so treat them as optional. 402 insufficient_tokens means the price is above the balance: narrow the filters or choose a plan. On a paid plan, waiting for resets_at also works; on the free plan's window, resets_at is its end and no tokens follow it. upgrade_url is absent on a custom plan. DELIVERY Accept gzip and check Content-Encoding: standard, dutching and raw answer a request that refuses gzip (Accept-Encoding: identity) with 400. Status, health, usage, quotes and errors are always plain. Store each ETag exactly as sent (quotes included) and send it back as If-None-Match: 304 means unchanged, no body, no tokens. Quotes carry no ETag. Rate limits are per account (X-RateLimit-*). On 429 honour Retry-After; our edge's own 429 and 502 have an empty body. RESPONSE SHAPE (matched products) { "meta": { "feed_type", "region", "odds_format", "processed_at", "last_seen", "count", "version", "next_cursor" }, "data": [ { "event_id", "sport_key", "sport_title", "commence_time", "home_team", "away_team", "markets": [ { "key", "outcomes": [ { "name", "back": [...], "lay": [...] } ] } ] } ] } lay is an array on standard, 2up and bog; an object {win: [...], place: [...]} on each-way and extra-place; absent on dutching, where back legs carry dutch_id (group by it; a dutch rates 100 / sum(1/price)). Event ids are stable across routes but re-key once when a higher-ranked exchange lists the fixture: don't store them as permanent keys. Every level can be empty; walk it defensively. ERRORS Every JSON error is { "error": { "code", "message", "request_id" } }, plus the extra fields listed. 400 bad_request, missing_param, region_required, unknown_bookmaker (valid), unknown_exchange (valid), unknown_market (products, valid), unknown_sport (valid) -> fix the request; do not retry. 401 expired_api_key, invalid_api_key, missing_api_key, paused_api_key, revoked_api_key -> stop and surface it; do not retry. 402 insufficient_tokens (cost, plan, quote, rate_card, remaining, resets_at, upgrade_url) -> out of tokens: add filters, choose a plan, or on a paid plan wait for X-Tokens-Reset; price a call first with quote=true. 403 account_suspended (dashboard_url, reason), bookmaker_not_in_plan (valid), bookmaker_not_in_region (type), exchange_not_in_plan (valid), market_not_in_plan (valid), not_in_key_scope (type), origin_not_allowed, product_not_in_region (type), region_not_in_scope (type), schema_not_in_scope (type), sport_not_in_plan (valid) -> refused for this account, key, region or plan; do not retry. 404 not_found, unknown_event, unknown_region, unknown_type -> a bad path or value. 405 method_not_allowed -> use GET. 429 too_many_from_address, too_many_requests -> back off and honour Retry-After. 500 internal_error -> transient; retry with backoff. 503 temporarily_unavailable -> transient; retry with backoff, honouring Retry-After. 503 raw_not_enabled -> raw odds are not served here: stop and report it; do not retry. Branch on code, never on message. Log request_id. WHAT TO BUILD [Describe what you want here.] ``` -------------------------------- ### GET /v2/events Source: https://oddsrelay.io/docs Returns events for a sport. This endpoint is free and does not require a key. ```APIDOC ## GET /v2/events ### Description Returns events for a sport. ### Method GET ### Endpoint /v2/events ### Parameters #### Query Parameters - **sport** (string) - Required - The sport for which to list events. ### Authentication No key needed. ### Cost Free ``` -------------------------------- ### GET /v2/coverage Source: https://oddsrelay.io/docs/guides/coverage-method.md Retrieves coverage information for a specified region. Without a key, returns public statuses (live or interrupted_24h) per product, bookmaker, and sport. With a key, returns the key's products with event counts and last_seen times. ```APIDOC ## GET /v2/coverage ### Description Retrieves coverage information for a specified region. Without a key, returns public statuses (live or interrupted_24h) per product, bookmaker, and sport. With a key, returns the key's products with event counts and last_seen times. ### Method GET ### Endpoint /v2/coverage ### Parameters #### Query Parameters - **region** (string) - Optional - Region code. Defaults to 'uk' if missing. ### Authentication - **Without a key**: No Authorization header and no x-api-key header. Public and cacheable. - **With a key**: Send either key header (Authorization or x-api-key). An empty or invalid key returns 401. ### Request Example ``` curl --compressed -s "https://api.oddsrelay.io/v2/coverage?region=uk" ``` ### Response #### Success Response (200) - **Without a key**: Per product, each bookmaker and sport is live (serving now) or `interrupted_24h` (not serving now, but served within the last 24 hours). No counts and no times. - **With a key**: The key's products that the region serves, bookmakers and exchanges apart, each with the sports it has rows for now, their event counts and `last_seen`. #### Response Example (No explicit response example provided in the source.) ### Error Handling - **401**: Returned when an empty or invalid key is sent with the request. ``` -------------------------------- ### GET /v2/sports Source: https://oddsrelay.io/docs Returns sports and competitions, with their markets. This endpoint is free and does not require a key. ```APIDOC ## GET /v2/sports ### Description Returns sports and competitions, with their markets. ### Method GET ### Endpoint /v2/sports ### Authentication No key needed. ### Cost Free ``` -------------------------------- ### GET /v2/odds/event/{id} Source: https://oddsrelay.io/docs Returns raw prices for one event. Requires authentication. ```APIDOC ## GET /v2/odds/event/{id} ### Description Returns raw prices for one event. ### Method GET ### Endpoint /v2/odds/event/{id} ### Parameters #### Path Parameters - **id** (string) - Required - The event identifier. ### Authentication Requires a key sent in the Authorization header as Bearer or in x-api-key. Do not put the key in the query string. ### Cost Tokens ``` -------------------------------- ### Scan for new host elements after client-side navigation Source: https://oddsrelay.io/docs/widgets/install Call window.OddsRelay?.scan() after a client-side route change to mount host elements that were added after the bundle loaded. This is a one-call fix for dynamic content. ```javascript // After a client-side navigation, host elements that were not on the page // when the bundle loaded have not been seen. One call fixes it. window.OddsRelay?.scan(); ``` -------------------------------- ### cURL client for OddsRelay API (oddsrelay.sh) Source: https://oddsrelay.io/docs/guides/quickstart.md Complete cURL client for the OddsRelay API. Requires the ODDSRELAY_KEY environment variable. It quotes the call, makes it with gzip (--compressed), reads token headers, polls with If-None-Match, backs off on 429, and stops on 402. ```bash #!/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" ``` -------------------------------- ### GET /v2/coverage Source: https://oddsrelay.io/docs Returns what each bookmaker and exchange covers, by sport. The public view does not require a key. ```APIDOC ## GET /v2/coverage ### Description Returns what each bookmaker and exchange covers, by sport. ### Method GET ### Endpoint /v2/coverage ### Authentication No key needed for the public view. ### Cost Free ``` -------------------------------- ### OddsRelay API sample in JavaScript (oddsrelay.mjs) Source: https://oddsrelay.io/docs/guides/quickstart.md Complete OddsRelay API client in Node 18+ that quotes, calls, and polls the standard board. Requires ODDSRELAY_KEY environment variable; handles 429 with Retry-After, stops on 402, and uses ETag/If-None-Match for free 304 polls. ```javascript // OddsRelay API sample, JavaScript (Node 18+). Run: ODDSRELAY_KEY=or_live_… node oddsrelay.mjs // // 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 (fetch sends Accept-Encoding: gzip and decompresses for you) 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). const BASE = "https://api.oddsrelay.io"; const KEY = process.env.ODDSRELAY_KEY; if (!KEY) throw new Error("set ODDSRELAY_KEY"); // Football match odds at one bookmaker, UK & Ireland: a small call. const PATH = "/v2/odds/standard?region=uk&sports=soccer&markets=h2h&bookmakers=ladbrokes"; const MAX_TOKENS = 1000; // don't make the call if the quote is above this const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function call(path, headers = {}) { for (let attempt = 1; attempt <= 3; attempt++) { const res = await fetch(BASE + path, { headers: { Authorization: `Bearer ${KEY}`, ...headers } }); if (res.status === 429) { // The front door's own 429 has an empty body; both carry Retry-After. const wait = Number(res.headers.get("retry-after") ?? "1"); console.log(`429: waiting ${wait} s`); await sleep(wait * 1000); continue; } if (res.status === 402) { const { error } = await res.json(); throw new Error(`402 ${error.code}: ${error.cost} tokens needed, ${error.remaining} left until ${error.resets_at}`); } return res; } throw new Error("still rate-limited after 3 tries"); } const tokens = (res) => `cost=${res.headers.get("x-tokens-cost")} remaining=${res.headers.get("x-tokens-remaining")} reset=${res.headers.get("x-tokens-reset")}`; // 1. Quote: free. const quoteRes = await call(`${PATH}"e=true`); if (quoteRes.status !== 200) throw new Error(`quote answered ${quoteRes.status}`); const quote = await quoteRes.json(); console.log(`quote: ${quote.cost} tokens, ${quote.remaining} left`); if (quote.cost > MAX_TOKENS) throw new Error(`quote ${quote.cost} is above ${MAX_TOKENS}: narrow the filters`); // 2. The call. const res = await call(PATH); if (res.status !== 200) throw new Error(`call answered ${res.status}: ${await res.text()}`); const etag = res.headers.get("etag"); const { meta } = await res.json(); console.log(`200: ${meta.count} events, board time ${meta.processed_at}, ${tokens(res)}`); // 3. Poll with the ETag: 304 if nothing changed (free), else a fresh 200. const again = await call(PATH, { "If-None-Match": etag }); if (again.status !== 304 && again.status !== 200) throw new Error(`poll answered ${again.status}`); console.log(`${again.status} on poll: ${tokens(again)}`); console.log("ok"); ```