Parameters: sport_key selects the sport (for example afl or nrl); markets is a comma-separated list such as h2h,spreads,totals; bookmakers optionally filters bookmaker keys; oddsFormat accepts decimal or american. `markets` is deduplicated and validated before billing, so a repeated market is charged once and an unknown one is a free 422 rather than a paid empty list. There is no racing sport_key. For horse, harness or greyhound racing use GET /v1/racing/next-to-go (2 credits) or GET /v1/racing/events instead — passing 'horse-racing' here returns 404, because racing is a separate endpoint family. **Pre-match only. There is no in-play feed.** An event is listed until its `commence_time` and leaves this endpoint the moment it starts; no bookmaker connector reads in-play markets, so nothing here updates during a match. The last pre-match prices for an event that has started are kept in GET /v1/sports/{sport_key}/odds/history and the move feed at GET /v1/sports/{sport_key}/odds/movements. (Documented 2026-09-10 after a customer polled a game through kick-off expecting live prices.)
X-API-Key
1 credit per requested market
curl 'https://api.puntersedge.online/v1/sports/afl/odds' \
-H 'X-API-Key: YOUR_KEY'
| Name | In | Type | Required | Description |
|---|---|---|---|---|
sport_key |
path | string | yes | |
markets |
query | string | no | Comma-separated: h2h,spreads,totals |
bookmakers |
query | string | no | Comma-separated bookmaker keys, or omit for all. Case-insensitive. An unrecognised key is a free 422 naming the valid keys, so a typo costs nothing — it used to return a billed empty response. |
competition |
query | string | no | Filter to one competition, case-insensitive exact match (e.g. NRLW). A value this sport has never carried is a 422 listing the ones it has, not an empty 200 — see `include_unknown_competition` for why that distinction matters. See the `competition` field on each event. |
include_unknown_competition |
query | boolean | no | When filtering by `competition`, ALSO return events whose competition is null — not every bookmaker supplies one, and on some sports most of the card is unlabelled (ncaaf 40 of 47 upcoming events, tennis_atp 21 of 72). Because of this the filter is 'this competition OR unlabelled', so a result set is not proof that the competition matched anything: an unrecognised value is rejected with 422 rather than answering with the unlabelled events alone. Set false for a strict exact match that returns only events explicitly labelled with this competition. |
oddsFormat |
query | string | no | decimal or american |
maxAgeMinutes |
query | integer | no | Exclude bookmaker markets older than this many minutes; default 360 allows 3-hour supplemental feeds while hiding multi-day stale prices. |
Status codes: 200, 401, 402, 422, 429, 500. Response bodies are JSON; the full schema is in /openapi.json.
200 application/json
— this call costs 1 credit per requested market.
Field names and types are as the API returns them; values are a real sample, trimmed to a few items.
[
{
"id": "6b1608c5-a9b8-4235-9a12-6db9656fef74",
"sport_key": "nrl",
"sport_title": "NRL",
"competition": "NRL",
"odds_format": "decimal",
"commence_time": "2026-09-09T02:00:00Z",
"home_team": "Cronulla Sharks",
"away_team": "Nth Queensland Cowboys",
"bookmakers": [
{
"key": "sportsbet",
"title": "Sportsbet",
"last_update": "2026-09-07T00:34:45.060482",
"age_seconds": 41,
"stale": false,
"quality": {
"score": 99.9,
"status": "ok",
"age_seconds": 41,
"issues": [],
"source_count": 5,
"stale_after_seconds": 1800
},
"markets": [
{
"key": "h2h",
"quality": {
"score": 99.9,
"status": "ok",
"age_seconds": 41,
"issues": [],
"source_count": 5,
"stale_after_seconds": 1800
},
"outcomes": [
{
"name": "Cronulla Sharks",
"price": 1.44
},
{
"name": "Nth Queensland Cowboys",
"price": 2.81
}
]
},
{
"key": "spreads",
"quality": {
"score": 99.9,
"status": "ok",
"age_seconds": 41,
"issues": [],
"source_count": 5,
"stale_after_seconds": 1800
},
"outcomes": [
{
"name": "Cronulla Sharks",
"price": 1.87,
"point": -7.5
},
{
"name": "Nth Queensland Cowboys",
"price": 1.93,
"point": 7.5
}
]
},
{
"key": "totals",
"quality": {
"score": 99.9,
"status": "ok",
"age_seconds": 41,
"issues": [],
"source_count": 5,
"stale_after_seconds": 1800
},
"outcomes": [
{
"name": "Over",
"price": 1.89,
"point": 47.5
},
{
"name": "Under",
"price": 1.91,
"point": 47.5
}
]
}
]
},
{
"key": "pointsbetau",
"title": "Pointsbetau",
"last_update": "2026-09-07T00:48:33.497304",
"age_seconds": 12,
"stale": false,
"quality": {
"score": 100.0,
"status": "ok",
"age_seconds": 12,
"issues": [],
"source_count": 5,
"stale_after_seconds": 1800
},
"markets": [
{
"key": "h2h",
"quality": {
"score": 100.0,
"status": "ok",
"age_seconds": 12,
"issues": [],
"source_count": 5,
"stale_after_seconds": 1800
},
"outcomes": [
{
"name": "Cronulla Sharks",
"price": 1.42
},
{
"name": "North Queensland Cowboys",
"price": 2.85
}
]
},
{
"key": "spreads",
"quality": {
"score": 100.0,
"status": "ok",
"age_seconds": 12,
"issues": [],
"source_count": 5,
"stale_after_seconds": 1800
},
"outcomes": [
{
"name": "Cronulla Sharks",
"price": 1.9,
"point": -7.5
},
{
"name": "North Queensland Cowboys",
"price": 1.9,
"point": 7.5
}
]
}
]
},
{
"key": "tab",
"title": "Tab",
"last_update": "2026-09-07T00:48:33.220409",
"age_seconds": 12,
"stale": false,
"quality": {
"score": 100.0,
"status": "ok",
"age_seconds": 12,
"issues": [],
"source_count": 5,
"stale_after_seconds": 1800
},
"markets": [
{
"key": "h2h",
"quality": {
"score": 100.0,
"status": "ok",
"age_seconds": 12,
"issues": [],
"source_count": 5,
"stale_after_seconds": 1800
},
"outcomes": [
{
"name": "Cronulla",
"price": 1.43
},
{
"name": "Nth Qld",
"price": 2.85
}
]
},
{
"key": "totals",
"quality": {
"score": 100.0,
"status": "ok",
"age_seconds": 12,
"issues": [],
"source_count": 5,
"stale_after_seconds": 1800
},
"outcomes": [
{
"name": "Over",
"price": 1.87,
"point": 47.5
},
{
"name": "Under",
"price": 1.87,
"point": 47.5
}
]
}
]
}
],
"canonical_event_id": "nrl:cronulla-sharks:nth-queensland-cowboys:2026-09-09",
"data_quality": {
"score": 99.9,
"status": "ok",
"source_count": 5,
"bookmaker_count": 3
},
"fetched_at": "2026-09-07T00:48:33.497304",
"data_age_seconds": 41,
"freshest_age_seconds": 12,
"stale": false,
"stale_bookmakers": [],
"cached": false,
"cache_age_seconds": 0
}
]
GET /v1/sports — List available sportsGET /v1/sports/{sport_key}/closing-lines — Closing price and line per bookmaker, with the consensusGET /v1/sports/{sport_key}/odds/history — Historical odds snapshotsGET /v1/sports/{sport_key}/odds/movements — Price movement feedThe free tier needs no credit card, and the sandbox endpoints need no key at all.
Get a free API key Quickstart