On this page
  1. Endpoint mapping, old to new
  2. Behavioral differences
  3. Cut-over checklist
  4. Python snippets
  5. Node.js snippets
  6. REST drop buffer
  7. Verifying the cut-over
  8. More code and reference

Thank you for requesting the kit. Everything below is the document we wished had existed in August 2025: a map from the old Pinnacle API to what works now, the differences that break things, a checklist, and code you can paste. The longer reference behind the mapping is on the documentation page; the reasoning behind our choice of pinnodds is on the alternatives page.

Endpoint mapping, old to new

Every data endpoint of the old Pinnacle API has a direct pinnodds equivalent; the account and betting endpoints have none. Paths below are relative to https://pinnodds.com and authenticate with the x-api-key header.

Scroll for more

Old Pinnacle API endpoints mapped to their pinnodds equivalents, with notes
Old Pinnacle endpointpinnodds equivalentNote
/v1/sportsFixed ids: 1 Soccer, 2 Tennis, 3 Basketball, 4 Hockey, 5 Am. Football, 6 Baseball, 7 Rugby, 8 MMA, 9 Boxing, 10 Other, 11 Esports, 12 Golf, 13 CricketIds differ from Pinnacle's; SDKs export SPORTS
/v2/leaguesLeague fields inside fixtures and markets responsesNo separate league endpoint
/v1/fixtures/kit/v1/prematch/fixtures?sport_id=…since, include_specials=1 supported
/v1/odds, /v3/odds/kit/v1/markets?sport_id=…&event_type=live or prematchsince, include_specials=1 supported
/v1/line/kit/v1/prematch/lines?event_id=…&market_type=totalsLine ladder per market type
Per-event odds (client-side filter)/kit/v1/details?event_id=…, /kit/v1/prematch/markets?event_id=…New, dedicated calls
/v1/fixtures/special, /v1/odds/specialinclude_specials=1 on fixtures and marketsFolded into main responses
/v1/periodsPeriod id on each market row; 0 = full matchNo lookup endpoint
/v1/inrunningevent_type=live on marketsLive is an event property
Own polling for line moves/api/drops, SSE stream_drops, wss://pinnodds.com/ws/feed?key=KEYNew capability
/v1/fixtures/settledNot listed in public docs; check current docsKeep your results source
/v2/bets, /v1/client/balance, /v1/currenciesNoneData-only feed

Behavioral differences

Seven differences account for nearly every production surprise during a migration. Read them before you change any code.

  1. Auth header instead of Basic auth. Send x-api-key: KEY. No username, no password, no base64. Kit endpoints also accept ?key=, but keep that out of logs.
  2. No funded account, no IP allow-list. The key is the whole identity. Cloud egress IPs can rotate freely. Remove any allow-list management code.
  3. Rate limits by plan, not fair use. Trial and Stream allow 20 requests a minute, 100 an hour and 100 a day. Pro and Pro + SSE allow 10 a second, Scale 30 a second. Exceeding them returns a rate-limit error with a retry-after value; the SDKs raise RateLimitError carrying it.
  4. One stream per key. A second SSE or WebSocket connection on the same key disconnects the first. One consumer per key, or a relay you run.
  5. since cursors still work. Markets and prematch fixtures accept since and return a last value to feed into the next call. Keep your delta loop; change the paths.
  6. Two drop shapes. REST drop rows carry from, to, nvp and a computed drop_pct, plus event fields such as home, away, market, period, designation, points. SSE alerts carry sect, outcome, from_price, to_price, id, limit, nvp and no percentage; derive it as (1 − to ÷ from) × 100.
  7. Flatter responses and new sport ids. The old nested league, event, period document becomes a list of events with markets attached. Sport ids are pinnodds' own. Conventions that do not change: decimal prices, period 0 for the full match, handicaps from the home side, quarter lines as separate rows.

Cut-over checklist

Work through these ten items in order. Most teams finish the first six in an afternoon and spend the rest of the time on the parser and on measuring their real request rate.

  1. Sign up and obtain a key. pinnodds issues one in seconds, without a card or Pinnacle account.
  2. Make one request by hand with curl and save a real response for each endpoint you will use.
  3. Translate your sport id constants to pinnodds ids.
  4. Replace Basic auth with the x-api-key header; delete IP allow-list logic.
  5. Swap endpoint paths using the mapping table; drop the separate leagues and periods calls.
  6. Rewrite the parser for the flatter event list; keep period, handicap and decimal conventions.
  7. Keep the since loop, pointing it at the new paths and storing last per sport and event type.
  8. Add RateLimitError handling that sleeps for retry_after; run the 3-day demo and log your actual requests per minute.
  9. If you polled to detect drops, replace that loop with the drop buffer or the SSE stream, and pick a plan with SSE if you keep the stream.
  10. Choose the plan whose limit sits comfortably above your measured rate, using the pricing page, then switch DNS, cron or systemd from the old job to the new one.

Python snippets

Install with pip install pinnodds. Python 3.8 or later, one dependency. The three snippets cover a board fetch, a delta loop with rate-limit handling, and the SSE drop stream.

Live board

board.py
# pip install pinnodds
from pinnodds import Client, SPORTS

api = Client("YOUR_KEY")
board = api.markets(sport_id=SPORTS["soccer"], event_type="live")
print(len(board["events"]), "live events")

Delta loop with since

deltas.py
import time
from pinnodds import Client, SPORTS, RateLimitError

api = Client("YOUR_KEY")
last = None
while True:
    try:
        resp = api.markets(sport_id=SPORTS["soccer"], event_type="prematch", since=last)
    except RateLimitError as e:
        time.sleep(e.retry_after or 5)
        continue
    last = resp.get("last") or last
    for ev in resp["events"]:
        pass  # apply changes to your store
    time.sleep(60)

Drop stream over SSE

drops.py
from pinnodds import Client

api = Client("YOUR_KEY")
for d in api.stream_drops(min_drop=5):
    pct = (1 - d["to_price"] / d["from_price"]) * 100
    print(f'{d["home"]} v {d["away"]}  {d["sect"]} {d["outcome"]}  '
          f'{d["from_price"]} -> {d["to_price"]}  ({pct:.1f}%)  nvp={d.get("nvp")}')

Node.js snippets

Install with npm install pinnodds. Zero dependencies, TypeScript types included. Same three patterns as the Python versions.

Live board

board.mjs
// npm install pinnodds
import { Client, SPORTS } from "pinnodds";

const api = new Client(process.env.PINNODDS_KEY);
const board = await api.markets({ sportId: SPORTS.soccer, eventType: "live" });
console.log(board.events.length, "live events");

Delta loop with since

deltas.mjs
import { Client, SPORTS, RateLimitError } from "pinnodds";

const api = new Client(process.env.PINNODDS_KEY);
let last;
for (;;) {
  try {
    const resp = await api.markets({ sportId: SPORTS.soccer, eventType: "prematch", since: last });
    last = resp.last ?? last;
    for (const ev of resp.events) { /* apply changes */ }
  } catch (e) {
    if (e instanceof RateLimitError) { await new Promise(r => setTimeout(r, (e.retryAfter ?? 5) * 1000)); continue; }
    throw e;
  }
  await new Promise(r => setTimeout(r, 60_000));
}

Drop stream over SSE

drops.mjs
import { Client } from "pinnodds";

const api = new Client(process.env.PINNODDS_KEY);
for await (const d of api.streamDrops({ minDrop: 5 })) {
  const pct = (1 - d.to_price / d.from_price) * 100;
  console.log(d.home, "v", d.away, d.sect, d.outcome, d.from_price, "->", d.to_price, pct.toFixed(1) + "%");
}

REST drop buffer

If you do not want a long-lived connection, poll the drop buffer instead. This returns prematch drops of at least 2 percent from the last hour; add mode=live or sport_id to narrow it.

drops.sh
curl -H "x-api-key: YOUR_KEY" \
  "https://pinnodds.com/api/drops?mode=prematch&min_drop_pct=2&max_age_sec=3600"

Verifying the cut-over

A migration is done when the new feed produces the same decisions as the old one would have. Five checks catch nearly all regressions before they reach production.

  1. Event coverage. For one sport, list the prematch events from the new feed and compare against the fixtures your old store holds for the same window. Expect the sets to match once you account for events that started or settled in between.
  2. Sign of the handicap. Pick three spread markets and confirm that a negative value means the home side gives points in your parsed output. A flipped sign is the most common silent bug in a migration.
  3. Quarter lines. Confirm that a 2.25 total or a −0.75 handicap appears as its own row with its own price and is not merged into the nearest half line.
  4. Cursor continuity. Run the delta loop, stop it for five minutes, restart it with the stored last value, and confirm that changes made during the gap arrive on the first request.
  5. Rate limit behavior. Deliberately exceed the limit on the Trial plan and confirm your code sleeps for the retry-after value instead of retrying in a tight loop.

Keep the old parser's output for a day of historical data if you have it, and diff the two parsers' results on the same events. Where they differ, the new feed's flatter structure or a renamed field is usually the cause, and the pinnodds documentation is the reference to settle it.

More code and reference

Longer, runnable versions of everything here live on pinnacleapi.dev, and the provider's reference is the authority on field names.