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

US Open Tennis Odds API: Live Pinnacle Feed

How to pull live US Open tennis odds from Pinnacle with a pushed API: /kit/v1/markets, the /ws/feed socket, SSE drop alerts, real field names and code.

If you are polling a REST endpoint on a timer during a live tennis match, you are reading a photograph of a moving object. Tennis reprices on a discrete event every couple of minutes — every hold, every break point, every medical timeout — and a US Open tennis odds API only earns its keep if the prices arrive when Pinnacle changes them, not when your cron job wakes up.

This is the working version of that setup: seed the board from /kit/v1/markets, take the push from /ws/feed or the SSE drop stream, and know which field names you actually get on the wire.

Why tennis punishes a poll loop

Football drifts. Ninety minutes of slow movement, punctuated by a handful of goals. Tennis is different — the market has an opinion after every game, and a single break of serve against a big server can move a match-winner line further than most people expect. That move lands between two of your poll cycles and you never see the price that existed in the middle.

Then there is Grand Slam volume. First week at Flushing Meadows you have men's and women's singles running at once across a dozen courts, plus doubles and mixed. Loop over all of them and you burn your rate limit re-fetching events where nothing has changed, while the one match you care about moved four seconds ago and has already been suspended and reopened.

The push model inverts that. You hold state; the feed mutates it.

The endpoints that cover a Slam

Four REST calls and two streams, and you are done.

  • GET /kit/v1/markets?event_type=live — the live board. Filter to tennis, take what is in play.
  • GET /kit/v1/markets?event_type=prematch — same shape, tomorrow's order of play.
  • GET /kit/v1/prematch/fixtures — fixtures only. Cheaper than full markets when all you want is IDs and start times.
  • GET /kit/v1/details — the detail view for one event.

Streams:

  • /ws/feed — the raw WebSocket passthrough. Everything Pinnacle emits, pushed.
  • /odds-drop and /odds-drop-prematch — Server-Sent Events carrying only price drops, if you would rather not process the firehose.

Auth is one API key: x-api-key: YOUR_KEY on REST, or ?key= on the streams, because EventSource and browser WebSocket clients cannot set headers. That same key is your login. Full reference in the docs.

# How many tennis events are live right now
curl -H "x-api-key: $PINNODDS_KEY" \
  "https://pinnodds.com/kit/v1/markets?event_type=live" \
  | jq '[.[] | select(.sport == "Tennis")] | length'

# Order of play, IDs and start times only
curl -H "x-api-key: $PINNODDS_KEY" \
  "https://pinnodds.com/kit/v1/prematch/fixtures" | jq '.[0]'

Line depth is where thin feeds get exposed

Match winner is the easy part. Anyone can serve match winner. What you want on a US Open match is set betting, total games, game handicaps, set-level totals and handicaps — and the alternates around each of them, not just the "main" line someone decided to keep.

Every line Pinnacle prices comes through, quarter lines included. It is the same mechanism that gives you soccer totals at 1.75 / 2.25 / 2.75 / 3.25 and quarter-ball handicaps at -0.75 / -1.25 / -1.75 / -2.25: nothing gets rounded to the nearest whole number, nothing gets dropped for being an alternate. If Pinnacle made a price, it is in the payload.

Depth does not change with your plan. Plans differ on rate limit and push access only — see pricing.

Specials, and why the flag is off by default

Player props, exact scores, futures and outrights arrive as their own event rows when you pass include_specials=1. Each one carries special, special_category, special_markets and a parent_id pointing back at the parent fixture, so a player-aces total links cleanly to the match it belongs to instead of floating loose in the response.

The flag is off by default because specials dominate the payload. Concretely: soccer prematch is roughly 1,500 events without it and roughly 12,400 with it. Tennis behaves the same way during a Slam — the outright winner market plus per-match props multiplies your row count fast.

curl -H "x-api-key: $PINNODDS_KEY" \
  "https://pinnodds.com/kit/v1/prematch/markets?include_specials=1" \
  | jq '[.[] | select(.special_category != null)] | .[0]'

Turn it on when you are hunting props or the outright. Leave it off for a live match-winner bot. Mixing the two on one code path is how you make a live consumer sluggish for no benefit.

A worked example: watching one match

The SDKs wrap REST and SSE — npm install pinnodds or pip install pinnodds, both zero or low dependency.

import os
from pinnodds import Client

client = Client(api_key=os.environ["PINNODDS_KEY"])

# 1. Seed state from the live board
live = client.markets(event_type="live")
watch = {e["id"]: e for e in live if e.get("sport") == "Tennis"}

# 2. Let the drop stream mutate it
for alert in client.odds_drop():           # SSE: /odds-drop
    if alert["id"] not in watch:
        continue
    print(
        watch[alert["id"]]["name"],
        alert["sect"],                     # market, e.g. "Total Games"
        alert["outcome"],                  # the moving selection
        alert["from_price"], "->", alert["to_price"],
        "limit", alert["limit"],
        "nvp", alert["nvp"],
    )

Two fields in there do more work than the price itself.

limit is Pinnacle's max stake on that selection, and in live tennis it is the best available proxy for conviction. A move on a fat limit is information. The same move on a limit that just got slashed to a token amount usually means the trader is uncomfortable and about to suspend the market — that is a different event entirely, and your logic should treat it that way.

nvp is the no-vig fair price. If you are comparing Pinnacle to your own model, compare against nvp, not the offered price. Otherwise you are measuring the margin, not the opinion.

SSE alerts and REST drops are not the same object

People lose an afternoon to this in week one, so here it is plainly. An SSE alert from /odds-drop is the lean, instant form:

FieldMeaning
sectMarket name
outcomeMoving selection
from_price / to_priceBefore and after
idEvent ID
limitPinnacle max stake
nvpNo-vig fair price

There is no drop percentage on an SSE alert. If you want one, compute it.

A row from GET /api/drops?mode=live is the enriched form: market, designation, from, to, event_id, plus a precomputed drop_pct. Same concepts, different names, and the percentage is done for you.

The pattern I would build, and the one I would defend in review: stream /odds-drop for reaction time, and hit /api/drops?mode=live on a slower cadence as a reconciliation pass. It heals whatever your socket missed during a reconnect and hands you drop_pct without you recomputing it. Swap to mode=prematch overnight for the next day's order of play.

Where the push genuinely pays for itself

Between points, in play. Break point saved, price snaps back, and if your read is a poll interval old you are trading a number that no longer exists. This is the entire case for /ws/feed.

Retirement and injury risk. Tennis has the highest mid-event abandonment risk of the major sports, and the market usually knows before the scoreboard does — widening, limit cuts, suspension. Watching limit collapse on a live match is a real early signal, and it is free with every alert.

Overnight prematch drift. Slam draws move for a week before anyone tunes in. /odds-drop-prematch against /kit/v1/prematch/lines catches the slow bleed on a second-round matchup nobody is watching yet.

Where this feed is the wrong tool

It is Pinnacle only. If your edge is "compare Pinnacle to twelve soft books and bet the outlier", this gives you exactly one side of that comparison. You need an aggregator next to it. What you get here is the sharp reference price with full depth and nvp — the number you measure the other books against — and nothing about the other books.

There is no historical odds archive. Real-time feed, full stop. You cannot backtest a US Open closing-line-value model over the last five draws against this API. You would have to record the stream yourself, starting today, and wait a year.

No scores, no point-by-point. Odds, limits, no-vig prices. If your model needs to know who is serving at 4-4 in the third, that is a second data source and a join key you have to maintain yourself.

Push access is plan-gated. REST depth is identical everywhere, but SSE sits on Pro + SSE at $149/mo and Scale at $229/mo. Base Pro at $99/mo is REST-only. If your whole design assumes streaming, budget for it before you build against a poll loop, not after. Pricing.

Specials are heavy. include_specials=1 is not a flag to leave switched on. Pull the full prematch board with specials during a Slam and you will feel it; request it deliberately, cache aggressively.

Tennis suspends constantly. Between points, on challenges, on medical timeouts. Your consumer has to tolerate markets vanishing and returning at a different price, and must treat silence as "suspended" rather than "unchanged". That is the sport, not the API — and it is the single biggest source of bad logic in first-draft tennis bots.

Takeaway

Build the reader as a state machine, not a fetch loop. Seed once from GET /kit/v1/markets?event_type=live, key your events by id, then let /ws/feed or /odds-drop mutate that state as prices land. Reconcile against /api/drops?mode=live on a slow timer to heal reconnect gaps and pick up drop_pct for free. Treat a collapsing limit as a first-class signal alongside price — in live tennis it usually moves first. And keep specials on a separate, slower path from your live loop; the payload profiles are nothing alike.

Payload samples and the full endpoint reference are in the docs.

Frequently asked questions

Is there still a public Pinnacle API for tennis odds?

No. Pinnacle closed its own public API in July 2025. pinnodds is an independent service that carries the same market data, including live and prematch tennis, delivered as a push feed rather than a poll endpoint.

How do I get live US Open tennis odds from the API?

Call GET /kit/v1/markets?event_type=live with your x-api-key header and filter the response to tennis, then subscribe to /ws/feed or the /odds-drop SSE stream to receive price changes as Pinnacle makes them.

Does the tennis feed include player props and outright winner markets?

Yes, but only when you pass include_specials=1. Specials arrive as their own event rows with special, special_category, special_markets and a parent_id linking back to the parent fixture. The flag is off by default because specials dominate the payload.

Why does my SSE odds-drop alert have no drop percentage?

SSE alerts are the lean form: sect, outcome, from_price, to_price, id, limit and nvp, with no drop_pct field. Fetch GET /api/drops?mode=live for the enriched row, which includes a precomputed drop_pct.

Do I need a paid plan to stream tennis odds?

You can get a free trial key in seconds with no card, and REST line depth is identical on every plan. SSE push access sits on Pro + SSE at $149/mo and Scale at $229/mo; base Pro at $99/mo is REST-only.

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