Introduction
The BurnerOdds API delivers real-time sports odds through a single, unified REST + streaming interface. Its structure mirrors the OpticOdds v3 API, so existing OpticOdds integrations work with minimal changes.
All endpoints live under /api/v3. Every response wraps results in a top-level data array.
Base URL
…/api/v3
Authentication
Every request needs your API key, sent either as a header (recommended) or a query parameter:
# Header
curl "/api/v3/sports" -H "X-Api-Key: YOUR_KEY"
# Query param
curl "/api/v3/sports?key=YOUR_KEY"
Your key is licensed to specific sportsbooks. Requesting odds for a sportsbook your key isn't licensed for returns 403.
Quickstart
The typical flow: discover what's available, then pull odds.
# 1. list sports
curl "/api/v3/sports?key=YOUR_KEY"
# 2. leagues for a sport
curl "/api/v3/leagues?sport=soccer&key=YOUR_KEY"
# 3. active fixtures
curl "/api/v3/fixtures/active?sport=soccer&key=YOUR_KEY"
# 4. odds for a fixture (Pinnacle = ps3838)
curl "/api/v3/fixtures/odds?sportsbook=ps3838&fixture_id=FIXTURE_ID&key=YOUR_KEY"
# 5. for real-time updates, stream instead of polling
curl -N "/api/v3/stream/odds/soccer?sportsbook=ps3838&key=YOUR_KEY"
Odds formats
Pass odds_format on odds and streaming endpoints. Default is AMERICAN.
| Value | Example (from +150) |
|---|---|
AMERICAN | 150 |
DECIMAL | 2.50 |
PROBABILITY | 0.4 |
HONG_KONG | 1.5 |
INDONESIAN | 1.5 |
MALAY | -0.6667 |
Sportsbooks
Sportsbooks are referenced by id. Currently available:
| Brand | id |
|---|---|
| Pinnacle | ps3838 |
More sportsbooks are added over time — call /api/v3/sportsbooks for the live list.
GET/api/v3/sports
All sports (and their main markets). /api/v3/sports/active returns only sports with live odds.
{ "data": [ { "id": "soccer", "name": "Soccer", "numerical_id": null,
"main_markets": [ { "id": "moneyline", "name": "Moneyline" } ] } ] }
GET/api/v3/leagues
Leagues, optionally filtered by sport. /leagues/active = only leagues with live odds.
| Param | |
|---|---|
sport | filter by sport id (e.g. soccer) |
{ "data": [ { "id": "england_-_premier_league", "name": "England - Premier League",
"sport": { "id":"soccer", "name":"Soccer" }, "region":"ENGLAND" } ] }
GET/api/v3/sportsbooks
All sportsbooks; is_active flags those currently serving odds.
{ "data": [ { "id":"ps3838", "name":"PS3838", "logo":"…", "is_active":true } ] }
GET/api/v3/markets
Markets available for a sport — pass ?sport= to get the live list (moneyline, spreads, totals, period markets, player props, specials…). /market-types lists market-type metadata.
curl "/api/v3/markets?sport=baseball&key=YOUR_KEY"
{ "data": [ { "id":"player_strikeouts", "name":"Player Strikeouts" }, … ] }
GET /api/v3/markets/active — markets currently offered for specific fixtures on specific books (requires fixture_id + sportsbook). GET /api/v3/sportsbooks/active returns only live books.
GET/api/v3/teams
Teams/participants. Requires one of sport, league, or id. Paginated.
GET/api/v3/fixtures
Fixtures (games/events). /fixtures/active returns only non-completed fixtures with odds — the recommended starting point. Requires one of sport, league, id.
| Param | |
|---|---|
sport, league, id | filters (repeatable) |
status, is_live | filter by fixture status / live flag |
team_id | fixtures involving a team (repeatable) |
start_date_after, start_date_before | time window (ISO 8601 or YYYY-MM-DD) |
page | page number (50/page) |
updated_since | only fixtures changed since a timestamp (ISO 8601) |
{ "data": [ { "id":"PIN123", "game_id":"123", "start_date":"2026-08-22T14:00:00Z",
"home_team_display":"Arsenal", "away_team_display":"Chelsea",
"status":"unplayed", "is_live":false,
"sport":{"id":"soccer","name":"Soccer"}, "league":{"id":"england_-_premier_league","name":"England - Premier League"},
"has_odds":true } ], "page":1, "has_more":true }
GET/api/v3/fixtures/odds
Current odds. If an odd isn't returned it is, for all intents, suspended.
| Param | |
|---|---|
| sportsbook | required; repeatable (max 5), e.g. ps3838 |
| fixture_id | one of fixture_id / team_id / player_id; repeatable (max 5) |
market | filter by market id (e.g. moneyline, player_strikeouts) |
is_main | true = main lines only (drop alternates) |
odds_format | see Odds formats |
{ "data": [ { …fixture fields…, "odds": [
{ "id":"123:ps3838:moneyline:arsenal", "sportsbook":"PS3838",
"market":"Moneyline", "market_id":"moneyline", "name":"Arsenal", "is_main":true,
"selection":"Arsenal", "selection_line":null, "points":null, "price":1.95,
"player_id":null, "team_id":"T_arsenal", "timestamp":1784900000.12,
"grouping_key":"default", "limits":{"max":5000} } ] } ] }
GET/api/v3/fixtures/odds/historical
Recorded line movement for a single fixture. Params: fixture_id (single), sportsbook, market, include_timeseries=true, include_locked, odds_format.
{ "data":[ { …fixture…, "odds":[ { …descriptor…,
"entries":[ {"price":1.90,"timestamp":1784800000,"points":2.5,"locked":false} ],
"olv":{"price":1.90,"points":2.5}, "clv":{"price":1.95,"points":2.5} } ] } ] }
SSE/api/v3/stream/odds/{sport}
Server-Sent Events — a long-lived GET that pushes odds changes within milliseconds. Use this instead of polling for real-time freshness. {sport} is a path param.
| Param | |
|---|---|
| sportsbook | required; repeatable (≤5) |
league, fixture_id, market | filters (repeatable) |
is_main, is_live | boolean filters |
odds_format | see Odds formats |
last_entry_id | reconnect: replays only events after this id (exactly-once). Omit on a fresh connect to receive the full current-odds snapshot instead. |
On a fresh connect (no last_entry_id) the stream first sends the current odds as a snapshot — one or more odds events (chunked) with the present state of every odd matching your filters — then switches to live deltas. On reconnect, pass last_entry_id to skip the snapshot and replay only what you missed.
curl -N "/api/v3/stream/odds/soccer?sportsbook=ps3838&odds_format=DECIMAL&key=YOUR_KEY"
event: connected
data: ok go
event: odds # ← initial snapshot (current state), then live deltas
id: 1730079534820-2
data: {"data":[ {…odd…} ],"entry_id":"1730079534820-2","type":"odds"}
WS/api/v3/ws/odds/{sport}
The same stream over WebSocket (convenient for browsers & most languages). Same query params as SSE. Each message is a JSON object with a type.
const ws = new WebSocket("/api/v3/ws/odds/soccer?sportsbook=ps3838&key=YOUR_KEY");
ws.onmessage = e => {
const m = JSON.parse(e.data);
if (m.type === "odds") console.log(m.data);
};
Streaming event model
| Event | Meaning |
|---|---|
connected | handshake (data: ok go) |
ping | heartbeat every 5s (data = ISO timestamp) |
odds | initial snapshot on connect + every new/updated price afterwards — upsert by the odd id |
locked-odds | suspended/removed — mark unavailable |
fixture-status | opt-in via include_fixture_updates=true — fixture status / start-time changes (old_status→new_status) |
Every delta carries an entry_id (<millis>-<seq>). Track the last one; on reconnect pass it as last_entry_id to replay missed events (exactly-once). Wrap the connection in a retry loop with backoff.
Pagination
List endpoints return page and has_more. Request ?page=1, 2, … until has_more is false.
Errors & rate limits
| Code | |
|---|---|
401 | missing / invalid / revoked key |
403 | key not licensed for the requested sportsbook |
400 | bad request (missing required param, invalid odds_format, >5 sportsbooks) |
429 | rate limit exceeded — see Retry-After header |
Rate limits (per API key, per 15-second window): standard endpoints 2,500, new stream/ws connections 250, historical odds 10. A long-lived stream connection doesn't keep consuming quota. Over-limit returns 429 with Retry-After.
Errors return { "error": "…" }. For sub-minute freshness use streaming, not tight polling.