GET /api/events
Returns distinct events (matches) that currently have bookmaker odds. Each event has aslug you pass to GET /api/events/{id} to retrieve all market odds.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
sport | string | — | Filter by sport name (e.g. Football, Basketball, Tennis) |
competition | string | — | Filter by competition code (e.g. EPL, NBA, LIG1) |
bookmaker | string | — | Only events where this bookmaker has odds |
is_live | boolean | — | true = in-play only, false = pre-match only |
stale_minutes | integer | 10 | Max age of odds to include (1–60 min) |
limit | integer | 200 | Max events returned, up to 1000 |
Response
{
"data": [
{
"slug": "Crystal%20Palace%20-%20West%20Ham%20United",
"match_name": "Crystal Palace - West Ham United",
"competition": "EPL",
"sport": "Football",
"match_date": "2026-04-20T20:00:00Z",
"is_live": false,
"bookmaker_count": 7
},
{
"slug": "PSG%20-%20Lyon",
"match_name": "PSG - Lyon",
"competition": "LIG1",
"sport": "Football",
"match_date": "2026-04-20T21:00:00Z",
"is_live": false,
"bookmaker_count": 5
}
],
"meta": {
"count": 1616,
"rate_limit_remaining": 297
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
slug | string | URL-encoded match_name — pass to GET /api/events/{id} |
match_name | string | Canonical match name as stored by the scraper (e.g. "PSG - Lyon") |
competition | string | Competition code (e.g. EPL, LIG1) |
sport | string | Sport derived from competition code |
match_date | ISO-8601 string | null | Scheduled kick-off time (UTC), null if unknown |
is_live | boolean | true if any bookmaker has this event live right now |
bookmaker_count | integer | Number of distinct bookmakers covering this event |
The same fixture can appear multiple times with different
match_name values if different
bookmakers use different team name formats. Use bookmaker_count to identify the canonical
listing (highest count = most coverage = most canonical name).GET /api/events/
Returns all bookmaker odds and Pinnacle reference lines for a single event, grouped by(bookmaker × market_type × period). This is the most granular endpoint — every odds line for every market from every bookmaker for one match.
Path Parameter
| Parameter | Description |
|---|---|
id | URL-encoded match name from the slug field in GET /api/events |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
bookmaker | string | — | Filter to a single bookmaker |
market_type | string | — | Filter to a single market type (e.g. moneyline, total, spread) |
period | integer | — | Filter to a single period (0=full-time, 1=first-half) |
stale_minutes | integer | 20 | Max age of odds to include (1–60 min) |
Response
{
"data": {
"match_name": "Crystal Palace - West Ham United",
"competition": "EPL",
"sport": "Football",
"match_date": null,
"slug": "Crystal%20Palace%20-%20West%20Ham%20United",
"markets": [
{
"bookmaker": "Betsson",
"market_type": "moneyline",
"period": 0,
"selections": [
{ "selection": "Crystal Palace", "odds": 3.40 },
{ "selection": "Draw", "odds": 3.25 },
{ "selection": "West Ham United", "odds": 2.10 }
],
"scraped_at": "2026-04-20T14:11:09Z",
"url": "https://www.betsson.fr/paris-sportifs/event/1234.1"
},
{
"bookmaker": "Betsson",
"market_type": "total",
"period": 0,
"selections": [
{ "selection": "Over 2.5", "odds": 1.87 },
{ "selection": "Under 2.5", "odds": 1.98 }
],
"scraped_at": "2026-04-20T14:11:09Z",
"url": "https://www.betsson.fr/paris-sportifs/event/1234.1"
},
{
"bookmaker": "Betsson",
"market_type": "total",
"period": 1,
"selections": [
{ "selection": "Over 0.5", "odds": 1.22 },
{ "selection": "Under 0.5", "odds": 3.90 }
],
"scraped_at": "2026-04-20T14:11:09Z",
"url": "https://www.betsson.fr/paris-sportifs/event/1234.1"
},
{
"bookmaker": "Winamax",
"market_type": "moneyline",
"period": 0,
"selections": [
{ "selection": "Crystal Palace", "odds": 3.35 },
{ "selection": "Draw", "odds": 3.20 },
{ "selection": "West Ham United", "odds": 2.15 }
],
"scraped_at": "2026-04-20T14:07:00Z",
"url": "https://www.winamax.fr/paris-sportifs/sports/..."
}
],
"pinnacle": [
{
"market_type": "moneyline",
"period": 0,
"selections": [
{ "selection": "Crystal Palace", "odds": 3.20 },
{ "selection": "Draw", "odds": 3.15 },
{ "selection": "West Ham United", "odds": 2.00 }
],
"scraped_at": "2026-04-20T13:47:16Z"
}
],
"summary": {
"bookmakers": ["1xbet", "Bet365", "Betify", "Betsson", "Cloudbet", "Duel", "Mystake", "Polymarket", "Winamax"],
"market_types": ["3way_handicap", "btts", "moneyline", "player_prop", "spread", "team_total", "total"],
"total_markets": 57
}
},
"meta": {
"rate_limit_remaining": 296
}
}
markets Array Fields
Each object in markets represents all the odds a single bookmaker has for one market at one period:
| Field | Type | Description |
|---|---|---|
bookmaker | string | Bookmaker name |
market_type | string | Market type — see Market Types |
period | integer | Time period — see Period Values |
selections | array | All outcomes for this market. Each has selection (string) and odds (decimal float) |
scraped_at | ISO-8601 string | When this bookmaker’s odds were last scraped |
url | string | null | Direct deep-link to the bookmaker’s page for this event |
pinnacle Array Fields
Same structure as markets but without bookmaker or url. Pinnacle is the sharp reference line — it represents the sharpest market consensus. Compare any bookmaker’s odds against Pinnacle to estimate edge.
summary Fields
| Field | Type | Description |
|---|---|---|
bookmakers | string[] | All bookmakers covering this event (sorted alphabetically) |
market_types | string[] | All market types available across all bookmakers (sorted) |
total_markets | integer | Total (bookmaker × market_type × period) groups in this response |
Finding value: Compare
markets[n].selections[k].odds against the corresponding selection
in pinnacle. When a bookmaker’s odds are higher than Pinnacle’s fair price for the same
outcome, that’s a potential +EV opportunity. See GET /api/value-bets for pre-calculated
EV detections.Drilldown Examples
# All markets for one event
curl "https://api.oddsstream.io/api/events/Crystal%20Palace%20-%20West%20Ham%20United" \
-H "X-Api-Key: os_live_YOUR_KEY"
# Moneyline only
curl "https://api.oddsstream.io/api/events/Crystal%20Palace%20-%20West%20Ham%20United?market_type=moneyline" \
-H "X-Api-Key: os_live_YOUR_KEY"
# Betsson only
curl "https://api.oddsstream.io/api/events/Crystal%20Palace%20-%20West%20Ham%20United?bookmaker=Betsson" \
-H "X-Api-Key: os_live_YOUR_KEY"
# Betsson's 1st-half totals
curl "https://api.oddsstream.io/api/events/Crystal%20Palace%20-%20West%20Ham%20United?bookmaker=Betsson&market_type=total&period=1" \
-H "X-Api-Key: os_live_YOUR_KEY"
Full Python Example
import requests, urllib.parse
API_KEY = "os_live_YOUR_KEY"
BASE = "https://api.oddsstream.io"
# Step 1: find EPL events
resp = requests.get(
f"{BASE}/api/events",
params={"competition": "EPL", "limit": 10},
headers={"X-Api-Key": API_KEY}
)
events = resp.json()["data"]
print(f"Found {len(events)} EPL events")
# Step 2: pick an event and get all its markets
event = events[0]
slug = event["slug"] # already URL-encoded
resp2 = requests.get(
f"{BASE}/api/events/{slug}",
headers={"X-Api-Key": API_KEY}
)
detail = resp2.json()["data"]
print(f"Match: {detail['match_name']}")
print(f"Bookmakers: {detail['summary']['bookmakers']}")
print(f"Markets available: {detail['summary']['market_types']}")
# Step 3: find all moneyline odds across bookmakers
moneyline_markets = [m for m in detail["markets"] if m["market_type"] == "moneyline" and m["period"] == 0]
for market in moneyline_markets:
odds_str = ", ".join(f"{s['selection']} {s['odds']}" for s in market["selections"])
print(f" {market['bookmaker']}: {odds_str}")
JavaScript Example
const API_KEY = "os_live_YOUR_KEY";
const BASE = "https://api.oddsstream.io";
// Get all Football events with 5+ bookmakers
const resp = await fetch(`${BASE}/api/events?sport=Football&limit=100`, {
headers: { "X-Api-Key": API_KEY }
});
const { data: events } = await resp.json();
const deepCoverage = events.filter(e => e.bookmaker_count >= 5);
// Get one event's full detail
const event = deepCoverage[0];
const detailResp = await fetch(`${BASE}/api/events/${event.slug}`, {
headers: { "X-Api-Key": API_KEY }
});
const { data } = await detailResp.json();
// Compare Betsson moneyline against Pinnacle
const betssonML = data.markets.find(m => m.bookmaker === "Betsson" && m.market_type === "moneyline" && m.period === 0);
const pinnacleML = data.pinnacle.find(p => p.market_type === "moneyline" && p.period === 0);
if (betssonML && pinnacleML) {
betssonML.selections.forEach(sel => {
const pin = pinnacleML.selections.find(p => p.selection === sel.selection);
if (pin && sel.odds > pin.odds) {
const edge = ((sel.odds / pin.odds) - 1) * 100;
console.log(`${sel.selection}: Betsson ${sel.odds} > Pinnacle ${pin.odds} (+${edge.toFixed(1)}%)`);
}
});
}