Getting your API key
Open the Keys page
Go to Dashboard → Keys. Your production key is listed there.
Copy your key
Click Copy. The key is shown once in full — after that it’s masked in the dashboard. If you lose it, rotate it to get a new one.Key format: os_live_ followed by 32 hex characters.os_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4
Using the key
Every request must include your key in the X-Api-Key header:
curl "https://api.oddsstream.io/api/odds" \
-H "X-Api-Key: os_live_YOUR_KEY"
Storing the key securely
Always use environment variables. Never hardcode your key in source code.
bash / shell
Node.js
Python
.gitignore
# Add to your shell profile or .env file
export ODDSSTREAM_API_KEY=os_live_YOUR_KEY
# .env (never commit this file)
ODDSSTREAM_API_KEY=os_live_YOUR_KEY
import "dotenv/config"; // npm install dotenv
const API_KEY = process.env.ODDSSTREAM_API_KEY;
if (!API_KEY) throw new Error("ODDSSTREAM_API_KEY is not set");
const resp = await fetch("https://api.oddsstream.io/api/odds", {
headers: { "X-Api-Key": API_KEY }
});
# .env (never commit this file)
ODDSSTREAM_API_KEY=os_live_YOUR_KEY
import os
from dotenv import load_dotenv # pip install python-dotenv
load_dotenv()
API_KEY = os.environ["ODDSSTREAM_API_KEY"] # raises KeyError if missing
import requests
resp = requests.get(
"https://api.oddsstream.io/api/odds",
headers={"X-Api-Key": API_KEY}
)
# Prevent accidental commits
.env
.env.local
.env.*.local
Never expose your API key in client-side JavaScript. Any key in browser code can be extracted and abused. Always call the API from your server and proxy results to your frontend. Never commit keys to git — use environment variables or a secrets manager.
Key management
From Dashboard → Keys:
| Action | What it does |
|---|
| Copy | Re-copies the masked key — save it when you first create it |
| Rotate | Generates a new key immediately. The old key stops working instantly. Use this if your key was leaked. |
| Revoke | Permanently disables the key. You’ll need to create a new one. |
Error responses
| Status | Code | What it means | Fix |
|---|
401 | unauthorized | Key missing, empty, or doesn’t exist | Check the header name and key value |
403 | live_not_on_plan | Requested live odds without Pro Live plan | Upgrade to Pro+ Live |
403 | websocket_not_on_plan | Requested WS token without a plan that includes WebSocket | Upgrade to Pro+ RT or Pro+ Live |
403 | bookmaker_not_configured | Free plan: no bookmaker selected in dashboard | Visit Dashboard → Billing to pick one |
403 | bookmaker_forbidden | Free plan: requesting a different bookmaker than your selected one | Switch to your selected bookmaker |
429 | rate_limit_exceeded | Too many requests | Back off and retry after X-RateLimit-Reset |
401 vs 403 in plain English:
401 = “I don’t know who you are” — the key is wrong or missing
403 = “I know who you are, but you can’t access this” — check the specific code for the fix
Every response includes:
X-RateLimit-Remaining: 187
X-RateLimit-Reset: 1745160060
When X-RateLimit-Remaining drops below 20, slow down:
const resp = await fetch(url, { headers });
const remaining = Number(resp.headers.get("X-RateLimit-Remaining") ?? 999);
if (remaining < 20) await new Promise(r => setTimeout(r, 5_000));
import time
resp = requests.get(url, headers=headers)
remaining = int(resp.headers.get("X-RateLimit-Remaining", 999))
if remaining < 20:
time.sleep(5)
Plan limits
| Plan | Rate limit | WebSocket | Endpoints |
|---|
| Free | 200 req/day | 30 connections/day | Sports, Bookmakers, Events, Odds (1 bookmaker) |
| Pro | 5,500 req/hr | Not included | + Value Bets, all bookmakers |
| Pro+ RT | 5,500 req/hr | Unlimited | + WebSocket Stream |
| Pro+ Live | 5,500 req/hr | Unlimited | + Live in-play odds, WebSocket Stream |
Upgrade at oddsstream.io/pricing.