Feed liveChannel BlogSports 12
Transport MQTT / SSE / RESTPush 30–150 msV1
API & Data

Pinnacle API Shut Down: NFL Odds Alternative

The Pinnacle API shut down in July 2025. Here's a working alternative for NFL odds: push feeds, real field names, a migration order, and where it's the wrong tool.

If your NFL model still calls pinnacle.com/v1/fixtures, it has been returning nothing useful since July 2025, when Pinnacle closed its public API. The replacement you want carries the same market data, and you want it wired up before Week 1 — not on a Sunday morning when a line is moving and you're staring at a schema mismatch in a terminal.

What follows is the migration I'd actually run. It assumes you had a polling integration against the old API, you care about sides, totals and the alternate ladders around them, and you have roughly a weekend.

This is not a URL swap

The old Pinnacle API was polling REST. Ask for fixtures, ask for odds with a since cursor, get a delta, sleep, ask again. That loop leaked everywhere in your codebase: a scheduler, a cursor store, backoff, a "did anything change" diff, and a mental model in which odds are something you fetch.

A push feed inverts that. pinnodds delivers odds over a WebSocket at /ws/feed and odds-drop alerts over Server-Sent Events. REST is still there and you'll lean on it — snapshots, reconnect backfill, enrichment — but the steady state is a socket you hold open while the game runs.

That inversion is the real work in an NFL odds API migration. The lines map over cleanly. Your polling scaffolding does not. Try to preserve it by hammering REST on a timer and you'll buy a rate limit you don't need and still be late to a Thursday-night scratch.

The endpoints you'll actually call

Keep this list open in a second tab. Full reference is in the docs.

  • GET /kit/v1/markets?event_type=live — live markets. Same endpoint, event_type=prematch, for pregame.
  • GET /kit/v1/details — detail view for a single event.
  • GET /kit/v1/prematch/fixtures — what's on the board.
  • GET /kit/v1/prematch/markets and GET /kit/v1/prematch/lines — prematch pricing, alternates included.
  • GET /api/drops?mode=live (or mode=prematch) — enriched odds-drop rows.
  • /ws/feed — raw WebSocket passthrough. The firehose.
  • /odds-drop and /odds-drop-prematch — SSE alert streams.
  • /health and /ping — for your monitoring, not your business logic.

One API key everywhere: x-api-key: YOUR_KEY as a header on REST, or ?key= as a query parameter on the streams, because browsers and most SSE clients can't set headers. That key is also your login. Keep it server-side.

Two drop shapes, and why they differ

The trap: assuming the SSE alert and the REST drop row share a schema. They don't, and the split is intentional.

An SSE event from /odds-drop is the fast form. Market in sect, moving selection in outcome, prices in from_price and to_price, event in id. It also carries limit — Pinnacle's max stake on that selection — and nvp, the no-vig fair price. What it does not carry is a drop percentage. Compute it yourself if you want one.

A row from GET /api/drops is the enriched form: market, designation, from, to, event_id, plus a precomputed drop_pct.

ConceptSSE /odds-dropREST /api/drops
Market namesectmarket
Selectionoutcomedesignation
Old pricefrom_pricefrom
New priceto_priceto
Event keyidevent_id
Drop size(compute it)drop_pct
Max stakelimit
No-vig pricenvp

Alert off SSE, reconcile off REST. The stream wakes your bot up; the REST rows feed your dashboard and your post-hoc analysis, because drop_pct already exists there and you do not want two implementations of the same arithmetic quietly drifting apart over a season.

A worked example: filtering NFL total moves

Here's the minimal listener with the official Node SDK (npm install pinnodds; Python is the same shape via pip install pinnodds).

import { PinnoddsClient } from "pinnodds";

const client = new PinnoddsClient({ apiKey: process.env.PINNODDS_KEY });

// Snapshot first, so you have state before the stream starts mutating it.
const board = await client.get("/kit/v1/prematch/markets", { sport: "football" });
const state = new Map(board.events.map(e => [e.id, e]));

client.onOddsDrop(evt => {
  const { id, sect, outcome, from_price, to_price, limit, nvp } = evt;
  const dropPct = ((from_price - to_price) / from_price) * 100;

  // limit gate first. Then market. Then size.
  if (limit < 2000) return;
  if (!sect.includes("Total")) return;
  if (dropPct <= 3) return;

  console.log(`${id} ${sect} ${outcome}: ${from_price} -> ${to_price} (nvp ${nvp})`);
  // reconcile against /api/drops before acting
});

The limit gate is doing most of the work in that snippet, and it's the part people skip. Say a Sunday 1pm total is priced 1.95 on the over and ticks to 1.87. If Pinnacle's max stake on that selection is small, the market hasn't woken up — that move is a trader nudging an untested number, and it will get nudged back. The same 1.95 → 1.87 with a large limit attached, on the same game, means real money got taken at the old price. Identical drop_pct, completely different meaning.

Filtering on limit before drop_pct killed more false positives for me than every smoothing scheme I tried. Order matters: cheap integer comparison first, string match second, arithmetic last.

Line depth: delete your interpolation code

Every line Pinnacle prices comes through. For NFL that's the full alternate spread and total ladders, not just the headline number. Quarter lines are in there too, which matters most if you also run soccer — totals at 1.75 / 2.25 / 2.75 / 3.25, handicaps at -0.75 / -1.25 / -1.75 / -2.25.

Depth is identical on every plan. Plans differ in rate limit and push access, not in which lines you can see. So if the old API's rate limits pushed you into deriving alternates by interpolation, that code can go. It was always an approximation; now it's an unnecessary one.

For player props, exact scores, futures and division winners, pass include_specials=1. Each special arrives as its own event row carrying special, special_category, special_markets and a parent_id pointing back to the parent fixture — so an anytime-TD-scorer market joins cleanly to the game it belongs to.

Be deliberate with that flag. It's off by default because specials dominate the payload: soccer prematch is roughly 1,500 events without it and roughly 12,400 with it. NFL has its own props explosion. Turn it on for the ingest job that populates your props tables. Leave it off for the loop that only needs sides and totals.

Where this feed is the wrong tool

Pinnacle only. That's the design, not an oversight. If your product is a line-shopping page across forty books, or you need to spot a soft book sitting a half-point off the market, one sharp source won't get you there. Use an aggregator — and honestly, use both: pinnodds for the reference price, an aggregator for the retail spread.

There is no historical odds archive. This is a real-time feed, not a research database. Want closing-line-value backtests for the 2023 NFL season? You can't get them here, and you can't get them by waiting. Stand up a time-series table on day one and start storing your own ticks, or discover the gap in November when you actually need it.

Odds-drop alerts are drops, singular direction. A price that lengthens is not a drop and will not fire on /odds-drop. If your strategy chases steam in both directions, sit on /ws/feed and write your own directional logic — the raw passthrough gives you everything, but the detection is yours.

And it's paid. A trial key takes seconds and needs no card, but there's no permanent free tier; paid plans start at $99/mo, with SSE and Scale tiers above that on pricing. For a hobby scoreboard, the economics don't work.

A migration order that survives Sunday

Six steps, each one rollback-able.

  1. Trial key, then /ping. Confirm auth works from your production network before touching application code.
  2. Shadow-read. Old integration stays the source of truth. Write pinnodds snapshots from /kit/v1/prematch/markets into a parallel table and diff for a day.
  3. Swap the snapshot path. Point the pregame board at /kit/v1/prematch/fixtures + /kit/v1/prematch/lines. Still polling, still boring, but now on the new source.
  4. Add the stream. Open /ws/feed or /odds-drop and let it mutate the same in-memory state your poller fills.
  5. Cut the poll interval. Once the stream is trusted, REST becomes reconnect backfill and reconciliation only.
  6. Enable include_specials=1 in a separate job, last, when nothing else is on fire.

Step 4 is where teams get burned, and the failure is silent. A socket that drops for ninety seconds during a two-minute drill leaves you holding stale prices with no error anywhere in your logs. Write the resnapshot-on-reconnect path before you write the message handler. Not after, not "once it's working" — before.

Takeaway

Treat the SSE alert and the REST drop row as two objects with two jobs: sect / outcome / from_price plus limit and nvp for fast filtering, market / designation / drop_pct for reconciliation and reporting. Gate on limit before drop size, re-snapshot on every reconnect, and keep include_specials off until the core board is stable. Everything else in this migration is mechanical. Field-by-field reference is in the docs, and the endpoint list above is the whole surface you need for NFL.

Frequently asked questions

Is there still a public Pinnacle API?

No. Pinnacle closed its public API in July 2025, so the old fixtures and odds endpoints no longer serve developers. pinnodds is an independent service that carries the same Pinnacle market data over REST, WebSocket and SSE.

What is the best alternative to the Pinnacle API for NFL odds?

If you need Pinnacle's own prices and full alternate ladders, pinnodds is the direct replacement — odds are pushed over a WebSocket at /ws/feed with SSE odds-drop alerts. If you need to compare forty sportsbooks, use an aggregator instead, or run both side by side.

Does pinnodds include NFL alternate spreads and totals?

Yes. Every line Pinnacle prices comes through, including the full alternate spread and total ladders, and depth is identical on every plan. Plans differ only in rate limit and push access.

How do I get NFL player props from the odds API?

Pass include_specials=1 on the request. Props, exact scores, futures and outrights then arrive as their own event rows with special, special_category, special_markets and a parent_id linking back to the parent fixture.

Does the odds-drop feed give me a drop percentage?

Only on REST. A /api/drops row includes a precomputed drop_pct, while an SSE alert from /odds-drop gives you from_price and to_price plus limit and nvp, and you compute the percentage yourself.

How much does a Pinnacle odds API alternative cost?

A trial key is free and takes seconds with no card. Paid plans start at $99/mo for Pro, $149/mo for Pro + SSE and $229/mo for Scale, with quarterly and semi-annual options.

Get real-time Pinnacle odds in your code

Live & prematch markets with instant odds-drop alerts over SSE and WebSocket. Free trial key in seconds — no card.

Start free trial