{"id":3657,"date":"2026-08-21T10:00:00","date_gmt":"2026-08-21T10:00:00","guid":{"rendered":"https:\/\/oddspapi.io\/blog\/?p=3657"},"modified":"2026-08-29T14:57:50","modified_gmt":"2026-08-29T14:57:50","slug":"nfl-schedule-api","status":"publish","type":"post","link":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/","title":{"rendered":"NFL Schedule API: Pull the Full 2026 Season in Python"},"content":{"rendered":"<p>The NFL does not publish a schedule API. ESPN&#8217;s old public endpoints are undocumented and break without notice, nflverse ships CSV dumps built for R users, and the licensed stats feeds put a sales call in front of a JSON file. None of that helps if you want the 2026 fixture list in your own database this afternoon.<\/p>\n<p>This guide pulls the whole loaded NFL season out of the OddsPapi <code>\/v4\/fixtures<\/code> endpoint in 21 API calls and 52 seconds, on the free key. Every number below came off the live API on August 12, 2026. All eight code blocks ran end to end before this post went out.<\/p>\n<h2>What you get, and what you do not<\/h2>\n<p>The endpoint returns fixtures: teams, kickoff times, status, a coverage flag, and a block of third-party IDs for joining to other data. It does not return scores, and it does not return a week number. Both gaps are fixable and this post shows how.<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>The old way<\/th>\n<th>OddsPapi <code>\/v4\/fixtures<\/code><\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Scrape ESPN&#8217;s undocumented JSON and re-fix the parser every season<\/td>\n<td>One documented endpoint, stable JSON shape<\/td>\n<\/tr>\n<tr>\n<td>Download a CSV dump and wait for someone to update it<\/td>\n<td>Live feed, <code>updatedAt<\/code> on every fixture<\/td>\n<\/tr>\n<tr>\n<td>Sales call before you see a schema<\/td>\n<td>Free key, curl it in 30 seconds<\/td>\n<\/tr>\n<tr>\n<td>Schedule from one provider, odds from another, IDs that never match<\/td>\n<td>Schedule and odds keyed on the same <code>fixtureId<\/code><\/td>\n<\/tr>\n<tr>\n<td>No way to join to Betradar or Sofascore<\/td>\n<td><code>externalProviders<\/code> on every fixture<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<h2>Step 1: authenticate<\/h2>\n<p>The key goes in the query string. It is not a header, and passing it as one gets you a 401.<\/p>\n<pre class=\"wp-block-code\"><code>import datetime as dt\nimport time\nimport requests\n\nAPI_KEY = \"YOUR_API_KEY\"\nBASE_URL = \"https:\/\/api.oddspapi.io\/v4\"\n\n\ndef fetch(path, **params):\n    \"\"\"GET a v4 endpoint. Returns None when the API says 'nothing here'.\"\"\"\n    params[\"apiKey\"] = API_KEY\n    for attempt in range(6):\n        r = requests.get(f\"{BASE_URL}\/{path}\", params=params, timeout=60)\n        if r.status_code == 429:\n            wait = r.json()[\"error\"].get(\"retryMs\", 1500) \/ 1000\n            time.sleep(wait + 0.3)\n            continue\n        if r.status_code == 404:\n            return None                      # empty window, not an error\n        r.raise_for_status()\n        return r.json()\n    raise RuntimeError(f\"rate limited on \/{path}\")\n\n\nprint(len(fetch(\"sports\")), \"sports\")        # 69<\/code><\/pre>\n<p>Two things in that function matter more than they look. A 429 response carries a JSON body with <code>retryMs<\/code>, so the API tells you exactly how long to wait rather than making you guess. And a 404 from <code>\/fixtures<\/code> means the window is empty, which happens 11 times in the season loop below. If you let <code>raise_for_status()<\/code> see it, your season pull dies in August.<\/p>\n<h3>The 404 is not an error<\/h3>\n<pre class=\"wp-block-code\"><code>GET \/v4\/fixtures?sportId=14&tournamentId=31&from=2026-08-12&to=2026-08-22\n\n404\n{\"error\": {\"message\": \"No fixtures found for the specified criteria.\",\n           \"code\": \"FIXTURE_NOT_FOUND\",\n           \"details\": \"Please check your filters and try again.\"}}<\/code><\/pre>\n<p>The NFL plays no games in late August, so the window is genuinely empty. Treat <code>FIXTURE_NOT_FOUND<\/code> as an empty list and move to the next window.<\/p>\n<h2>Step 2: find the NFL<\/h2>\n<p>American football is <code>sportId=14<\/code>, and that sport holds 29 tournaments. Six of them have fixtures loaded right now, including the college board with 3,507 of them. Filter on the wrong row and you get a schedule 26 times too big.<\/p>\n<pre class=\"wp-block-code\"><code>tours = fetch(\"tournaments\", sportId=14)\n\nfor t in tours:\n    if t[\"futureFixtures\"]:\n        print(f\"{t['tournamentId']:>6}  {t['tournamentName']:&lt;28} \"\n              f\"{t['categoryName']:&lt;14} {t['futureFixtures']} future\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>    31  NFL                          USA            132 future\n   233  NFL Preseason                USA             48 future\n   790  CFL                          Canada          43 future\n 27653  NCAA, Regular Season         USA           3507 future\n 51578  EFA                          International    2 future\n 51580  AFLE                         International    8 future<\/code><\/pre>\n<p>The NFL is <code>tournamentId=31<\/code>. Hardcode it after you have looked it up once. Preseason lives on its own ID (233), so a pull that expects one season list will silently miss August football.<\/p>\n<h3><code>tournamentId<\/code> works on \/fixtures, and it is worth using<\/h3>\n<p>The parameter is not in the endpoint docs but it filters server side. The same six-day window returned 324 American football fixtures unfiltered and 15 with <code>tournamentId=31<\/code> attached. That is 20 times less JSON to download and parse.<\/p>\n<h2>Step 3: pull the season<\/h2>\n<p><code>\/fixtures<\/code> takes a date range and caps it at 10 days. A season is about 25 weeks, so you loop. Deduplicate on <code>fixtureId<\/code> as you go, because consecutive windows share a boundary day.<\/p>\n<pre class=\"wp-block-code\"><code>def pull_season(tournament_id, start, end, window=10):\n    fixtures, calls, empty = {}, 0, 0\n    day = dt.date.fromisoformat(start)\n    last = dt.date.fromisoformat(end)\n    while day &lt; last:\n        to = min(day + dt.timedelta(days=window), last)\n        batch = fetch(\"fixtures\", sportId=14, tournamentId=tournament_id,\n                      **{\"from\": day.isoformat(), \"to\": to.isoformat()})\n        calls += 1\n        if batch is None:\n            empty += 1\n        else:\n            for f in batch:\n                fixtures[f[\"fixtureId\"]] = f\n        day = to\n        time.sleep(1.0)                      # per-endpoint cooldown\n    return list(fixtures.values()), calls, empty\n\n\nseason, calls, empty = pull_season(31, \"2026-08-12\", \"2027-03-05\")\nprint(f\"{len(season)} fixtures in {calls} calls ({empty} empty)\")\n# 132 fixtures in 21 calls (11 empty)<\/code><\/pre>\n<p>That run took 52 seconds, and 21 of those seconds are the deliberate <code>sleep(1.0)<\/code>. The free tier rate-limits per endpoint, and a one-second gap between calls to the same path returns clean 200s every time. Do not thread this. Concurrency at any worker count gets almost everything rejected.<\/p>\n<h3>Set <code>to<\/code> to the day after the last day you want<\/h3>\n<p>The window is inclusive of both midnight instants, which means <code>to<\/code> behaves as a timestamp rather than a whole day. Asking for one day gets you only the games that kick off at exactly 00:00 UTC:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Query<\/th>\n<th>Fixtures returned<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>from=2026-09-13&amp;to=2026-09-13<\/code><\/td>\n<td>0<\/td>\n<\/tr>\n<tr>\n<td><code>from=2026-09-13&amp;to=2026-09-14<\/code><\/td>\n<td>12<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Both queries cover the same Sunday slate. The first one makes the NFL look cancelled.<\/p>\n<h2>Step 4: the fixture object<\/h2>\n<p>Here is one game, unedited:<\/p>\n<pre class=\"wp-block-code\"><code>{\n  \"fixtureId\": \"id1400003171515752\",\n  \"sportId\": 14,\n  \"tournamentId\": 31,\n  \"tournamentName\": \"NFL\",\n  \"tournamentSlug\": \"nfl\",\n  \"categoryName\": \"USA\",\n  \"seasonId\": null,\n  \"statusId\": 0,\n  \"statusName\": \"Pre-Game\",\n  \"hasOdds\": true,\n  \"startTime\": \"2026-09-10T00:20:00.000Z\",\n  \"trueStartTime\": null,\n  \"trueEndTime\": null,\n  \"updatedAt\": \"2026-08-11T23:10:47.105Z\",\n  \"participant1Id\": 4430,\n  \"participant1Name\": \"Seattle Seahawks\",\n  \"participant1ShortName\": \"Seattle\",\n  \"participant1Abbr\": \"SEA\",\n  \"participant2Id\": 4424,\n  \"participant2Name\": \"New England Patriots\",\n  \"participant2ShortName\": \"New England\",\n  \"participant2Abbr\": \"NE\",\n  \"externalProviders\": {\n    \"betradarId\": 71515752,\n    \"opticoddsId\": \"20260910B2546DE0\",\n    \"sofascoreId\": 16184611,\n    \"betgeniusId\": 13906257,\n    \"flashscoreId\": \"GQtrH2RF\",\n    \"pinnacleId\": 1630865288,\n    \"mollybetId\": null,\n    \"lsportsId\": null,\n    \"txoddsId\": null,\n    \"oddinId\": null\n  }\n}<\/code><\/pre>\n<p>Three fields will bite you.<\/p>\n<p><strong><code>statusName<\/code> is missing, not null, on some fixtures.<\/strong> Sixteen of the 132 NFL games have no <code>statusName<\/code> key at all, and the same 16 have <code>statusId: null<\/code>. Every one of them is a real game with a real kickoff time. Use <code>f.get(\"statusName\", \"Unknown\")<\/code> and your parser survives.<\/p>\n<p><strong><code>seasonId<\/code> is null on all 132.<\/strong> Do not build a season key off it.<\/p>\n<p><strong>There is no week number.<\/strong> The feed gives you a kickoff timestamp and nothing else. Derive the week by anchoring on opening day, since the NFL runs a clean Thursday-to-Wednesday cycle:<\/p>\n<pre class=\"wp-block-code\"><code>season.sort(key=lambda f: f[\"startTime\"])\n\n\ndef utc(fixture):\n    return dt.datetime.fromisoformat(fixture[\"startTime\"].replace(\"Z\", \"+00:00\"))\n\n\nopening_day = utc(season[0]).date()\n\n\ndef week_of(fixture):\n    return (utc(fixture).date() - opening_day).days \/\/ 7 + 1\n\n\nweeks = {}\nfor f in season:\n    weeks.setdefault(week_of(f), []).append(f)\n\nfor w in sorted(weeks):\n    games = weeks[w]\n    print(f\"Week {w:>2}: {len(games):>2} games, \"\n          f\"{sum(g['hasOdds'] for g in games):>2} with odds\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>Week  1: 16 games, 16 with odds\nWeek  2: 16 games, 16 with odds\nWeek  3: 16 games, 16 with odds\nWeek  4: 16 games, 16 with odds\nWeek  5: 15 games, 15 with odds\nWeek  6: 14 games, 14 with odds\nWeek  7: 14 games, 14 with odds\nWeek  8: 14 games, 14 with odds\nWeek  9:  1 games,  1 with odds\nWeek 10:  1 games,  1 with odds\nWeek 11:  1 games,  0 with odds\nWeek 12:  4 games,  0 with odds\nWeek 16:  4 games,  0 with odds<\/code><\/pre>\n<p>The 16-game weeks are complete rounds for all 32 teams. Weeks 5 through 8 drop to 14 or 15 because byes start. After Week 8 the feed thins to a handful of marquee dates, and the four Week 12 games are Thanksgiving.<\/p>\n<h3>The season loads in front of you<\/h3>\n<p>132 fixtures is 48.5% of a 272-game regular season. Weeks 1 through 8 are fully loaded; the back half is not there yet, and no team has all 17 of its games on the feed. Re-run the loop weekly and merge on <code>fixtureId<\/code> rather than treating one pull as the finished article.<\/p>\n<h2>Step 5: kickoff times move a day<\/h2>\n<p>Every timestamp is UTC, and NFL prime-time kickoffs cross midnight when you convert. Twelve of the 132 games land on a Friday in UTC. None of them are Friday games.<\/p>\n<pre class=\"wp-block-code\"><code>EASTERN = dt.timezone(dt.timedelta(hours=-4))   # EDT during the season\n\nfor f in season[:5]:\n    local = utc(f).astimezone(EASTERN)\n    print(f\"{local:%a %b %d %H:%M} ET  \"\n          f\"{f['participant1Name']} v {f['participant2Name']}\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>Wed Sep 09 20:20 ET  Seattle Seahawks v New England Patriots\nThu Sep 10 20:35 ET  Los Angeles Rams v San Francisco 49ers\nSun Sep 13 13:00 ET  Indianapolis Colts v Baltimore Ravens\nSun Sep 13 13:00 ET  Detroit Lions v New Orleans Saints\nSun Sep 13 13:00 ET  Pittsburgh Steelers v Atlanta Falcons<\/code><\/pre>\n<p>Converted to Eastern, the slate structure appears exactly where you expect it: 54 games at Sunday 13:00, 19 at Sunday 16:25, seven Sunday nighters at 20:20, seven Monday nighters at 20:15, and four Sunday morning kickoffs at 09:30 that are the London games. Group by UTC date and you get none of that.<\/p>\n<h2>Step 6: join keys for everything else<\/h2>\n<p>The feed has no scores and no player stats, so most real pipelines join it to something that does. <code>externalProviders<\/code> is how. Coverage is uneven and worth measuring before you pick a key:<\/p>\n<pre class=\"wp-block-code\"><code>from collections import Counter\n\ncoverage = Counter()\nfor f in season:\n    for provider, value in (f.get(\"externalProviders\") or {}).items():\n        if value is not None:\n            coverage[provider] += 1\n\nprint({k: f\"{v}\/{len(season)}\" for k, v in coverage.most_common()})<\/code><\/pre>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Provider key<\/th>\n<th>Populated<\/th>\n<th>Type<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>betradarId<\/code><\/td>\n<td>132 of 132<\/td>\n<td>int<\/td>\n<\/tr>\n<tr>\n<td><code>opticoddsId<\/code><\/td>\n<td>117 of 132<\/td>\n<td>str<\/td>\n<\/tr>\n<tr>\n<td><code>sofascoreId<\/code><\/td>\n<td>30 of 132<\/td>\n<td>int<\/td>\n<\/tr>\n<tr>\n<td><code>flashscoreId<\/code><\/td>\n<td>30 of 132<\/td>\n<td>str<\/td>\n<\/tr>\n<tr>\n<td><code>betgeniusId<\/code><\/td>\n<td>16 of 132<\/td>\n<td>int<\/td>\n<\/tr>\n<tr>\n<td><code>pinnacleId<\/code><\/td>\n<td>16 of 132<\/td>\n<td>int<\/td>\n<\/tr>\n<tr>\n<td><code>lsportsId<\/code><\/td>\n<td>4 of 132<\/td>\n<td>int<\/td>\n<\/tr>\n<tr>\n<td><code>mollybetId<\/code><\/td>\n<td>1 of 132<\/td>\n<td>int<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Betradar is the only key present on every fixture, so build your join on that and fall back to team names plus kickoff date. Sofascore and Flashscore, the two free scoreboards a hobby project would reach for, cover 23%. The IDs also arrive at different types, so cast before you compare.<\/p>\n<h2>Step 7: <code>hasOdds<\/code> tells you a book exists, not that a market does<\/h2>\n<p>123 of the 132 fixtures carry <code>hasOdds: true<\/code>, which reads like the whole season is priced four months out. It is not. Measure the board instead of trusting the flag:<\/p>\n<pre class=\"wp-block-code\"><code>def board_depth(fixture_id):\n    payload = fetch(\"odds\", fixtureId=fixture_id)\n    books = (payload or {}).get(\"bookmakerOdds\") or {}\n    live = {s: b for s, b in books.items() if not b.get(\"suspended\")}\n\n    quotes = {}\n    for slug, book in live.items():\n        ml = book.get(\"markets\", {}).get(\"141\")        # Winner (incl. overtime)\n        if not ml:\n            continue\n        price = tuple(sorted(\n            (oid, round(o[\"players\"][\"0\"][\"price\"], 4))\n            for oid, o in ml[\"outcomes\"].items()\n            if o.get(\"players\", {}).get(\"0\", {}).get(\"active\")))\n        if len(price) == 2:\n            quotes.setdefault(price, []).append(slug)\n\n    markets = sum(len(b.get(\"markets\", {})) for b in live.values())\n    return len(live), len(quotes), markets<\/code><\/pre>\n<p>Note where <code>active<\/code> lives. An outcome object has exactly one key, <code>players<\/code>. The price, the <code>active<\/code> flag and the limit all sit one level deeper at <code>players[\"0\"]<\/code>, so <code>outcome[\"active\"]<\/code> is always undefined. On game lines the key is the string <code>\"0\"<\/code>; on player props the same dict is keyed by player ID.<\/p>\n<p>Run it across three fixtures on each of the first three Sunday slates, so the comparison is like for like:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Sunday slate<\/th>\n<th>Days out<\/th>\n<th><code>hasOdds<\/code><\/th>\n<th>Books<\/th>\n<th>Independent quotes<\/th>\n<th>Markets<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Week 1, Sep 13<\/td>\n<td>32<\/td>\n<td>true<\/td>\n<td>18<\/td>\n<td>13 to 14<\/td>\n<td>167 to 177<\/td>\n<\/tr>\n<tr>\n<td>Week 2, Sep 20<\/td>\n<td>39<\/td>\n<td>true<\/td>\n<td>4<\/td>\n<td>3<\/td>\n<td>20<\/td>\n<\/tr>\n<tr>\n<td>Week 3, Sep 27<\/td>\n<td>46<\/td>\n<td>true<\/td>\n<td>3<\/td>\n<td>2<\/td>\n<td>10 to 11<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>One week further out costs you four fifths of the board. Week 1 carries Pinnacle, SBOBet, Kalshi, Polymarket, Bet365, Circa and eleven others. Week 3 carries Caesars, William Hill and DraftKings, and Caesars and William Hill are the same feed quoting byte-identical prices. Two independent opinions, both flagged <code>hasOdds: true<\/code>.<\/p>\n<p>This is how sportsbooks work rather than a gap in the feed. Books hang the opening week first and fill the rest in as it approaches, and one desk carrying season-long placeholder numbers is enough to flip the flag. Prime-time standalone games hold depth longer than the Sunday slate around them: the Week 2 Friday game had 10 books while its Sunday slate had four.<\/p>\n<p><strong>Rule: a fixture count is not a coverage number.<\/strong> If your pipeline picks games to price, filter on measured depth, not on <code>hasOdds<\/code>.<\/p>\n<h3>What Week 1 looks like when the board is real<\/h3>\n<p>Seattle at New England, the first game on the feed, carried 174 markets across 18 bookmakers and 72 distinct market IDs. The moneyline, sorted by margin:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Bookmaker<\/th>\n<th>Seattle<\/th>\n<th>New England<\/th>\n<th>Margin<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>polymarket<\/td>\n<td>1.493<\/td>\n<td>2.941<\/td>\n<td>0.98%<\/td>\n<\/tr>\n<tr>\n<td>kalshi<\/td>\n<td>1.515<\/td>\n<td>2.778<\/td>\n<td>2.00%<\/td>\n<\/tr>\n<tr>\n<td>pinnacle<\/td>\n<td>1.490<\/td>\n<td>2.770<\/td>\n<td>3.22%<\/td>\n<\/tr>\n<tr>\n<td>circasports<\/td>\n<td>1.526<\/td>\n<td>2.650<\/td>\n<td>3.27%<\/td>\n<\/tr>\n<tr>\n<td>fanduel<\/td>\n<td>1.530<\/td>\n<td>2.600<\/td>\n<td>3.82%<\/td>\n<\/tr>\n<tr>\n<td>caesars = williamhill<\/td>\n<td>1.508<\/td>\n<td>2.640<\/td>\n<td>4.19%<\/td>\n<\/tr>\n<tr>\n<td>draftkings<\/td>\n<td>1.521<\/td>\n<td>2.600<\/td>\n<td>4.21%<\/td>\n<\/tr>\n<tr>\n<td>bet365<\/td>\n<td>1.500<\/td>\n<td>2.650<\/td>\n<td>4.40%<\/td>\n<\/tr>\n<tr>\n<td>hardrockbet<\/td>\n<td>1.526<\/td>\n<td>2.550<\/td>\n<td>4.75%<\/td>\n<\/tr>\n<tr>\n<td>sbobet<\/td>\n<td>1.480<\/td>\n<td>2.660<\/td>\n<td>5.16%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Polymarket and Kalshi price the opener tighter than Pinnacle does, which reproduces what the prediction markets have been doing on <a href=\"https:\/\/oddspapi.io\/blog\/college-football-odds-api\/\">college football<\/a> and <a href=\"https:\/\/oddspapi.io\/blog\/la-liga-odds-api\/\">La Liga<\/a> all summer. Read those as best available prices and nothing more; exchange quotes need a depth check on the <code>exchangeMeta<\/code> ladder before you treat them as real.<\/p>\n<p>Deduplicating the 18 books on the exact price tuple leaves 12 independent quotes. Some of those collapses are known shared feeds (Caesars with William Hill, BetMGM with Borgata, BetParx with BallyBet and FourWinds) and some are coincidence at a round number. Dedupe on the tuple first, then confirm against <code>\/v4\/bookmakers<\/code>, because <code>cloneOf<\/code> does not flag every shared feed.<\/p>\n<h2>Step 8: write it to CSV<\/h2>\n<pre class=\"wp-block-code\"><code>import csv\n\nwith open(\"nfl_2026_schedule.csv\", \"w\", newline=\"\") as fh:\n    writer = csv.writer(fh)\n    writer.writerow([\"week\", \"kickoff_utc\", \"kickoff_et\", \"home\", \"away\",\n                     \"status\", \"has_odds\", \"fixture_id\", \"betradar_id\"])\n    for f in season:\n        writer.writerow([\n            week_of(f),\n            f[\"startTime\"],\n            utc(f).astimezone(EASTERN).isoformat(),\n            f[\"participant1Name\"],\n            f[\"participant2Name\"],\n            f.get(\"statusName\", \"Unknown\"),\n            f[\"hasOdds\"],\n            f[\"fixtureId\"],\n            (f.get(\"externalProviders\") or {}).get(\"betradarId\"),\n        ])<\/code><\/pre>\n<p>132 rows, nine columns, ready for pandas or a <a href=\"https:\/\/oddspapi.io\/blog\/odds-database-python-sqlite\/\">SQLite table<\/a>. Keep <code>fixtureId<\/code> in the file. It is the key for every odds call you make later, and for the free <code>\/v4\/historical-odds<\/code> endpoint that gives you the full price history of any fixture back to its opening line.<\/p>\n<h2>The same loop, other competitions<\/h2>\n<p>Nothing in <code>pull_season<\/code> is NFL-specific past the tournament ID. Swap it and the loop works on any of the 69 sports:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Competition<\/th>\n<th><code>tournamentId<\/code><\/th>\n<th>Fixtures loaded<\/th>\n<th>With odds<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>NFL<\/td>\n<td>31<\/td>\n<td>132<\/td>\n<td>123<\/td>\n<\/tr>\n<tr>\n<td>NFL Preseason<\/td>\n<td>233<\/td>\n<td>48<\/td>\n<td>48<\/td>\n<\/tr>\n<tr>\n<td>NCAA regular season<\/td>\n<td>27653<\/td>\n<td>3,507 future<\/td>\n<td>97 in the opening 10 days<\/td>\n<\/tr>\n<tr>\n<td>CFL<\/td>\n<td>790<\/td>\n<td>43<\/td>\n<td>not measured<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>College is the interesting one. 3,507 future fixtures sounds like total coverage until you look at the opening window, where 416 games loaded and 97 had a price. The <a href=\"https:\/\/oddspapi.io\/blog\/college-football-odds-api\/\">NCAAF board<\/a> also runs 42% active, meaning most quoted outcomes are suspended lines a book posted early and stopped taking.<\/p>\n<h2>What is not in the feed<\/h2>\n<p>Being straight about the gaps saves you a wasted afternoon.<\/p>\n<ul>\n<li><strong>No scores or results.<\/strong> <code>statusName<\/code> reaches <code>Finished<\/code> and stops. Join out on <code>betradarId<\/code> or <code>sofascoreId<\/code>, or recover win and loss labels from the way historical prices collapse toward 1.00 after kickoff.<\/li>\n<li><strong>No player stats, no injuries, no venue, no weather.<\/strong> Teams, time, status, IDs.<\/li>\n<li><strong>No week number and no <code>seasonId<\/code>.<\/strong> Derive the week as shown above.<\/li>\n<li><strong>Half the season.<\/strong> 132 of 272 games today, front-loaded on Weeks 1 to 8.<\/li>\n<li><strong>No NFL outrights.<\/strong> The catalogue has no Super Bowl winner or division market. The only futures-shaped NFL market is To Win the Coin Toss.<\/li>\n<\/ul>\n<h2>Where the schedule pays off<\/h2>\n<p>A fixture list on its own is a calendar. Joined to the odds endpoint it becomes the spine of everything else: 350+ bookmakers on one <code>fixtureId<\/code>, free historical price history back to the opening line on the same key, and a WebSocket feed that pushes changes instead of making you poll for them. The Week 1 opener alone carries 72 distinct market IDs, including spreads, totals, team totals and anytime touchdown props keyed by player.<\/p>\n<p>Start with <a href=\"https:\/\/oddspapi.io\/blog\/free-nfl-odds-api-guide\/\">the NFL odds guide<\/a> for the market IDs, <a href=\"https:\/\/oddspapi.io\/blog\/nfl-key-numbers-half-point-cost\/\">key numbers<\/a> for what a half point is worth on the spread, and <a href=\"https:\/\/oddspapi.io\/blog\/free-sports-data-api\/\">the free sports data API guide<\/a> if you want the same loop pointed at the other 68 sports. New to the API? <a href=\"https:\/\/oddspapi.io\/blog\/first-odds-api-call-python\/\">Make your first call<\/a> first.<\/p>\n<h2>FAQ<\/h2>\n<h3>Is there a free NFL schedule API?<\/h3>\n<p>Yes. The OddsPapi <code>\/v4\/fixtures<\/code> endpoint returns the NFL schedule as JSON on the free tier. The full loaded 2026 season took 21 calls and 52 seconds on a free key.<\/p>\n<h3>What is the NFL tournament ID?<\/h3>\n<p>31, with slug <code>nfl<\/code> under category USA. Preseason is 233 and the college regular season is 27653. Sport ID 14 covers all of them plus the CFL, XFL and UFL, so filter on the tournament.<\/p>\n<h3>Why does my date query return nothing?<\/h3>\n<p>Two reasons. <code>to<\/code> is a midnight UTC instant, so set it to the day after the last day you want. And an empty window returns HTTP 404 with code <code>FIXTURE_NOT_FOUND<\/code>, which your fetch wrapper should treat as an empty list.<\/p>\n<h3>Does the API include NFL scores?<\/h3>\n<p>No. Fixtures carry teams, kickoff time and status only. Use the <code>externalProviders<\/code> block to join to a scores source. <code>betradarId<\/code> is populated on all 132 fixtures; <code>sofascoreId<\/code> and <code>flashscoreId<\/code> on 30 each.<\/p>\n<h3>How far ahead are NFL odds available?<\/h3>\n<p>Books price the opening week properly and thin out fast after it. Week 1 fixtures carried 18 bookmakers and up to 177 markets; the Week 3 Sunday slate carried three books and 10 markets. Every one of those fixtures reports <code>hasOdds: true<\/code>, so measure the board rather than trusting the flag.<\/p>\n<h3>Can I get last season&#8217;s NFL schedule and odds?<\/h3>\n<p><code>\/v4\/historical-odds<\/code> keeps deep price history per fixture, but 2025 regular-season NFL history has aged out. January 2026 playoff games still return data from retail books, and Super Bowl LX is fully retained.<\/p>\n<h2>Get your key<\/h2>\n<p>The schedule loop above runs on the free tier with no card and no sales call. <a href=\"https:\/\/oddspapi.io\/\">Grab a free API key<\/a>, point <code>pull_season<\/code> at tournament 31, and have the 2026 fixture list in your database before kickoff.<\/p>\n<p><script type=\"application\/ld+json\">\n{\n  \"@context\": \"https:\/\/schema.org\",\n  \"@type\": \"FAQPage\",\n  \"mainEntity\": [\n    {\"@type\": \"Question\", \"name\": \"Is there a free NFL schedule API?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Yes. The OddsPapi \/v4\/fixtures endpoint returns the NFL schedule as JSON on the free tier. The full loaded 2026 season took 21 calls and 52 seconds on a free key.\"}},\n    {\"@type\": \"Question\", \"name\": \"What is the NFL tournament ID?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"31, with slug nfl under category USA. Preseason is 233 and the college regular season is 27653. Sport ID 14 covers all of them plus the CFL, XFL and UFL, so filter on the tournament.\"}},\n    {\"@type\": \"Question\", \"name\": \"Why does my date query return nothing?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Two reasons. The to parameter is a midnight UTC instant, so set it to the day after the last day you want. And an empty window returns HTTP 404 with code FIXTURE_NOT_FOUND, which your fetch wrapper should treat as an empty list.\"}},\n    {\"@type\": \"Question\", \"name\": \"Does the API include NFL scores?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"No. Fixtures carry teams, kickoff time and status only. Use the externalProviders block to join to a scores source. betradarId is populated on all 132 fixtures; sofascoreId and flashscoreId on 30 each.\"}},\n    {\"@type\": \"Question\", \"name\": \"How far ahead are NFL odds available?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Books price the opening week properly and thin out fast after it. Week 1 fixtures carried 18 bookmakers and up to 177 markets; the Week 3 Sunday slate carried three books and 10 markets. Every one of those fixtures reports hasOdds true, so measure the board rather than trusting the flag.\"}},\n    {\"@type\": \"Question\", \"name\": \"Can I get last season's NFL schedule and odds?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"The \/v4\/historical-odds endpoint keeps deep price history per fixture, but 2025 regular-season NFL history has aged out. January 2026 playoff games still return data from retail books, and Super Bowl LX is fully retained.\"}}\n  ]\n}\n<\/script><\/p>\n<p><!--\nFocus Keyphrase: nfl schedule api\nSEO Title: NFL Schedule API: Pull the Full 2026 Season in Python (Free JSON)\nMeta Description: Pull the 2026 NFL schedule as free JSON in 21 Python calls. Week numbers, timezone traps, Betradar join keys and live odds on the same fixture ID.\nSlug: nfl-schedule-api\n--><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Pull the 2026 NFL schedule as free JSON in 21 Python calls. Week numbers, timezone traps, Betradar join keys and live odds on the same fixture ID.<\/p>\n","protected":false},"author":2,"featured_media":3658,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[8,52,9,11,10],"class_list":["post-3657","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to-guides","tag-free-api","tag-nfl","tag-odds-api","tag-python","tag-sports-betting-api"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>NFL Schedule API: Pull the Full 2026 Season in Python | OddsPapi Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"NFL Schedule API: Pull the Full 2026 Season in Python | OddsPapi Blog\" \/>\n<meta property=\"og:description\" content=\"Pull the 2026 NFL schedule as free JSON in 21 Python calls. Week numbers, timezone traps, Betradar join keys and live odds on the same fixture ID.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/\" \/>\n<meta property=\"og:site_name\" content=\"OddsPapi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-21T10:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-08-29T14:57:50+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/nfl-schedule-api-scaled.webp\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1429\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/webp\" \/>\n<meta name=\"author\" content=\"Odds API Writer\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/oddspapi.io\/logo-v2.webp\" \/>\n<meta name=\"twitter:creator\" content=\"@oddspapiapi\" \/>\n<meta name=\"twitter:site\" content=\"@oddspapiapi\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Odds API Writer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/\"},\"author\":{\"name\":\"Odds API Writer\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13\"},\"headline\":\"NFL Schedule API: Pull the Full 2026 Season in Python\",\"datePublished\":\"2026-08-21T10:00:00+00:00\",\"dateModified\":\"2026-08-29T14:57:50+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/\"},\"wordCount\":2023,\"commentCount\":4,\"publisher\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/nfl-schedule-api-scaled.webp\",\"keywords\":[\"Free API\",\"NFL\",\"Odds API\",\"Python\",\"Sports Betting API\"],\"articleSection\":[\"How To Guides\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/\",\"url\":\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/\",\"name\":\"NFL Schedule API: Pull the Full 2026 Season in Python | OddsPapi Blog\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/nfl-schedule-api-scaled.webp\",\"datePublished\":\"2026-08-21T10:00:00+00:00\",\"dateModified\":\"2026-08-29T14:57:50+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#primaryimage\",\"url\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/nfl-schedule-api-scaled.webp\",\"contentUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/nfl-schedule-api-scaled.webp\",\"width\":2560,\"height\":1429,\"caption\":\"NFL Schedule API - OddsPapi API Blog\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/oddspapi.io\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"NFL Schedule API: Pull the Full 2026 Season in Python\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\",\"url\":\"https:\/\/oddspapi.io\/blog\/\",\"name\":\"OddsPapi\",\"description\":\"Sports Odds API Tutorials &amp; Guides\",\"publisher\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\"},\"alternateName\":\"Odds Papi\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/oddspapi.io\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\",\"name\":\"OddsPapi\",\"url\":\"https:\/\/oddspapi.io\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png\",\"contentUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png\",\"width\":135,\"height\":135,\"caption\":\"OddsPapi\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/oddspapiapi\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13\",\"name\":\"Odds API Writer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g\",\"caption\":\"Odds API Writer\"},\"url\":\"https:\/\/oddspapi.io\/blog\/author\/andy-lavelle\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"NFL Schedule API: Pull the Full 2026 Season in Python | OddsPapi Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/","og_locale":"en_US","og_type":"article","og_title":"NFL Schedule API: Pull the Full 2026 Season in Python | OddsPapi Blog","og_description":"Pull the 2026 NFL schedule as free JSON in 21 Python calls. Week numbers, timezone traps, Betradar join keys and live odds on the same fixture ID.","og_url":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/","og_site_name":"OddsPapi Blog","article_published_time":"2026-08-21T10:00:00+00:00","article_modified_time":"2026-08-29T14:57:50+00:00","og_image":[{"width":2560,"height":1429,"url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/nfl-schedule-api-scaled.webp","type":"image\/webp"}],"author":"Odds API Writer","twitter_card":"summary_large_image","twitter_image":"https:\/\/oddspapi.io\/logo-v2.webp","twitter_creator":"@oddspapiapi","twitter_site":"@oddspapiapi","twitter_misc":{"Written by":"Odds API Writer","Est. reading time":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#article","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/"},"author":{"name":"Odds API Writer","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13"},"headline":"NFL Schedule API: Pull the Full 2026 Season in Python","datePublished":"2026-08-21T10:00:00+00:00","dateModified":"2026-08-29T14:57:50+00:00","mainEntityOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/"},"wordCount":2023,"commentCount":4,"publisher":{"@id":"https:\/\/oddspapi.io\/blog\/#organization"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/nfl-schedule-api-scaled.webp","keywords":["Free API","NFL","Odds API","Python","Sports Betting API"],"articleSection":["How To Guides"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/","url":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/","name":"NFL Schedule API: Pull the Full 2026 Season in Python | OddsPapi Blog","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#primaryimage"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/nfl-schedule-api-scaled.webp","datePublished":"2026-08-21T10:00:00+00:00","dateModified":"2026-08-29T14:57:50+00:00","breadcrumb":{"@id":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#primaryimage","url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/nfl-schedule-api-scaled.webp","contentUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/nfl-schedule-api-scaled.webp","width":2560,"height":1429,"caption":"NFL Schedule API - OddsPapi API Blog"},{"@type":"BreadcrumbList","@id":"https:\/\/oddspapi.io\/blog\/nfl-schedule-api\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/oddspapi.io\/blog\/"},{"@type":"ListItem","position":2,"name":"NFL Schedule API: Pull the Full 2026 Season in Python"}]},{"@type":"WebSite","@id":"https:\/\/oddspapi.io\/blog\/#website","url":"https:\/\/oddspapi.io\/blog\/","name":"OddsPapi","description":"Sports Odds API Tutorials &amp; Guides","publisher":{"@id":"https:\/\/oddspapi.io\/blog\/#organization"},"alternateName":"Odds Papi","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/oddspapi.io\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/oddspapi.io\/blog\/#organization","name":"OddsPapi","url":"https:\/\/oddspapi.io\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png","contentUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png","width":135,"height":135,"caption":"OddsPapi"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/oddspapiapi"]},{"@type":"Person","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13","name":"Odds API Writer","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g","caption":"Odds API Writer"},"url":"https:\/\/oddspapi.io\/blog\/author\/andy-lavelle\/"}]}},"_links":{"self":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/3657","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/comments?post=3657"}],"version-history":[{"count":2,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/3657\/revisions"}],"predecessor-version":[{"id":3804,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/3657\/revisions\/3804"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media\/3658"}],"wp:attachment":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media?parent=3657"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/categories?post=3657"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/tags?post=3657"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}