Quickstart
Three calls, no setup beyond a key.
1. Get a key
Get a free API key. It travels in the X-API-Key request header on every call; see Authentication for the two calls that use a signed-in session instead.
2. List a sport's fixtures
Every sport we cover has a key like americanfootball_nfl or soccer_epl (see Sport keys). Pass one to GET /v1/sports/{sport}/odds to get every upcoming fixture with every book's price for the markets you ask for:
/v1/sports/americanfootball_nfl/odds?markets=h2h,spreadscurl "https://oddslink.app/v1/sports/americanfootball_nfl/odds?markets=h2h,spreads" \
-H "X-API-Key: ok_live_..."import requests
resp = requests.get(
"https://oddslink.app/v1/sports/americanfootball_nfl/odds",
params={"markets": "h2h,spreads"},
headers={"X-API-Key": "ok_live_..."},
timeout=10,
)
resp.raise_for_status()
data = resp.json()
print(resp.headers["x-requests-remaining"])const res = await fetch(
"https://oddslink.app/v1/sports/americanfootball_nfl/odds?markets=h2h,spreads",
{ headers: { "X-API-Key": "ok_live_..." } }
);
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET",
"https://oddslink.app/v1/sports/americanfootball_nfl/odds?markets=h2h,spreads", nil)
req.Header.Set("X-API-Key", "ok_live_...")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}<?php
$ch = curl_init("https://oddslink.app/v1/sports/americanfootball_nfl/odds?markets=h2h,spreads");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: ok_live_..."],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);[
{
"id": "nfl:falcons-panthers:2026-09-20",
"sport_key": "americanfootball_nfl",
"sport_title": "NFL",
"commence_time": "2026-09-20T17:00:00Z",
"home_team": "Atlanta Falcons",
"away_team": "Carolina Panthers",
"bookmakers": [
{
"key": "betmgm",
"title": "BetMGM",
"last_update": "2026-09-18T18:08:20Z",
"markets": [
{
"key": "h2h",
"last_update": "2026-09-18T18:08:20Z",
"outcomes": [
{
"name": "Atlanta Falcons",
"price": 124
},
{
"name": "Carolina Panthers",
"price": -148
}
]
},
{
"key": "spreads",
"last_update": "2026-09-18T18:08:14Z",
"outcomes": [
{
"name": "Atlanta Falcons",
"price": 100,
"point": 2.5
},
{
"name": "Carolina Panthers",
"price": -120,
"point": -2.5
}
]
}
]
},
{
"key": "fanduel",
"title": "FanDuel",
"last_update": null,
"markets": [
{
"key": "h2h",
"last_update": null,
"outcomes": [
{
"name": "Atlanta Falcons",
"price": 120
},
{
"name": "Carolina Panthers",
"price": -142
}
]
},
{
"key": "spreads",
"last_update": null,
"outcomes": [
{
"name": "Atlanta Falcons",
"price": -102,
"point": 2.5,
"size": {
"kind": "posted_limit",
"amount": 5000
}
},
{
"name": "Carolina Panthers",
"price": -118,
"point": -2.5,
"size": {
"kind": "posted_limit",
"amount": 5000
}
}
]
}
]
}
],
"unmatched_bookmakers": [
"betano"
]
}
]3. Read the response
Each fixture's bookmakers[] carries that book's markets[], and each market carries outcomes[]. A book with no entry for this fixture simply is not in the list; a book that had a price but could not be matched to this fixture safely is named instead in unmatched_bookmakers, rather than silently dropped. This one call counts once against your plan's request allowance, regardless of how many fixtures or books come back.
That is the whole loop: a key, a sport key, and a markets list. The Odds guide covers period suffixes, sizes and formats in full; Props and History cover the other two endpoint families.