HomeBlog › How to Pull Pinnacle Player Props From the API (and the One Catch to Know First)
API & Data

How to Pull Pinnacle Player Props From the API (and the One Catch to Know First)

How to Pull Pinnacle Player Props From the API (and the One Catch to Know First)

Pinnacle player props are supported but hidden behind a flag. Here's how to turn them on with include_specials, the real special_markets payload shape, and the one behaviour that trips people up: spec

How to Pull Pinnacle Player Props From the API (and the One Catch to Know First)

Yes, you can get Pinnacle player props from the API. No, they will not show up in your normal feed, and there is one behaviour you want to understand before you build anything on top of them.

Here is the short version. Player props, team props, exact scores and futures are all treated as special markets. They are off by default. You switch them on with a single parameter, they come back as separate events joined to the main match, and, importantly, they do not fire drop alerts. If you skim nothing else, remember that last part, because a lot of the "why isn't my prop alert firing" confusion comes from not knowing it.

Why props aren't in the default feed

Pinnacle prices its main markets (money line, spreads, totals) at high limits and tight margins. That is the sharp line everyone anchors to. Props are a different animal on Pinnacle's book: fewer of them, lower limits, and they move on thinner information. Bundling them into the standard response would bloat every payload with markets most integrations never touch.

So the API keeps them separate. You opt in per request. If you never ask, you never pay the parsing cost.

Turning specials on

The parameter is include_specials=1. It works on the markets endpoints, live and prematch. Note the auth header: it is x-portal-apikey, not a Bearer token.

# Live events for a sport, including specials
curl -H "x-portal-apikey: $PINNAPI_KEY" \
 "https://pinnapi.com/kit/v1/markets?sport_id=3&include_specials=1"

# Prematch fixtures, including specials
curl -H "x-portal-apikey: $PINNAPI_KEY" \
 "https://pinnapi.com/kit/v1/prematch/fixtures?sport_id=6&include_specials=1"

sport_id is pinnapi's own integer id, not Pinnacle's. Basketball is 3, baseball is 6, soccer is 1. The full list lives in the sports and markets reference.

The shape you get back

The response is an object with an events array. A special is not a market bolted onto the main event. It is a separate event in that array, linked back to the match it belongs to by parent_id.

Here is the useful thing to know up front: every record carries a parent_id. On a main match it is null. On a special it holds the event_id of the parent match. That is how you tell the two apart, and it is not what most people assume before they look.

The prices sit under a special_markets object, keyed by period (num_0, num_1, ...), and each period holds a list of market objects. This is a real trimmed baseball response (verified against the live API, side and participant ids removed for readability):

{
  "event_id": 1633119018,
  "parent_id": 1632823960,
  "home": "Cincinnati Reds",
  "away": "Pittsburgh Pirates",
  "special": "Brandon Lowe Total Home Runs",
  "special_category": "Player Props",
  "special_units": "Home Runs",
  "special_markets": {
    "num_0": [
      {
        "type": "total",
        "key": "s;0;ou",
        "max_risk": 500,
        "prices": [
          { "name": "Over",  "points": 0.5, "price": 4.10 },
          { "name": "Under", "points": 0.5, "price": 1.21 }
        ]
      }
    ]
  }
}

Three fields name the market for you. special_category is the family: "Player Props", "Team Props", "Exact Scores". special is the specific market, the human-readable thing you would show a user ("Brandon Lowe Total Home Runs"). special_units tells you what is being counted ("Home Runs"). The naming of the family varies by sport: baseball and basketball surface Player Props, soccer leans on Team Props.

One structural gotcha worth stating loudly: special_markets.num_0 is a list, not a dict of named markets like the standard periods tree. You iterate it, then iterate prices inside each entry. Code that indexes it like the main markets will throw.

Joining specials back to their match

Because specials arrive as their own events, the work on your side is a join. Pull the list, split on parent_id, and hang each special under its parent:

import os, requests

KEY = os.environ["PINNAPI_KEY"]
BASE = "https://pinnapi.com/kit/v1"

def get_with_specials(sport_id):
    r = requests.get(
        f"{BASE}/prematch/fixtures",
        headers={"x-portal-apikey": KEY},
        params={"sport_id": sport_id, "include_specials": 1},
        timeout=10,
    )
    r.raise_for_status()
    return r.json()["events"]

events = get_with_specials(sport_id=6)  # baseball

# parent_id is null on a main event and set on a special
mains = {e["event_id"]: e for e in events if not e.get("parent_id")}
specials = [e for e in events if e.get("parent_id")]

for sp in specials:
    parent = mains.get(sp["parent_id"])
    where = f'{parent["home"]} vs {parent["away"]}' if parent else "match not in this pull"
    print(f'{sp["special_category"]}: {sp["special"]} on {where}')

And to read the actual prices out of one special:

sp = specials[0]
for period_markets in sp["special_markets"].values():  # e.g. the "num_0" list
    for market in period_markets:                       # a list of market objects
        for price in market["prices"]:
            print(sp["special"], price["name"], price.get("points"), "@", price["price"])

The one habit worth forming: guard the mains.get(sp["parent_id"]) lookup. A special can arrive in a response whose parent match is not in the same slice you pulled, and code that assumes the parent is always present will throw on a live board.

The catch: specials don't fire drop alerts

Straight from the docs: "Specials do not fire drop alerts."

If your pipeline leans on the odds-drop stream to catch moves, that stream is watching the main markets only. A prop line can shift and no drop event will land. This is not a bug to work around, it is the design, and the reasoning behind it holds up: props are lower-limit and less liquid, so their movement carries less signal than a money-line move at full limit. Alerting on every prop wobble would drown the signal you actually pay for.

You can see the limit gap in the data. In the response above, the prop market carried a max_risk of 500. The main run-line and totals on the same slate sit at 5000. That order-of-magnitude difference is exactly why props are treated as a lower-signal, separate class rather than fed into the alert stream.

The practical consequence: if you want to track a prop over time, you poll it. Snapshot the special on an interval that suits the market, diff it yourself, and set your own threshold for what counts as a meaningful move. You are doing by hand what the drop stream does for main markets, and for props that is the honest trade.

Before you build on Pinnacle props

Worth saying plainly, because it saves disappointment later: Pinnacle is not a props-first book. If you have used a US retail app, you are used to dozens of player props per game with generous limits. Pinnacle carries fewer, at lower limits, and not on every match. Where a prop exists, it is priced sharp, which is the whole reason to want it as a reference. But do not size a product around breadth of props on Pinnacle. Size it around the sharpness of the ones that are there.

If you need wide prop coverage across many books, that is an aggregator's job, and the honest comparison is in the 2026 provider breakdown.

Takeaway

Flip include_specials=1, split the events array on parent_id, read prices out of the special_markets lists by period, and remember that the drop stream will not alert you on any of it. Poll and diff props yourself. Do that and Pinnacle's sharp prop line is a clean input to your model, with no surprises about where the alerts went.

Frequently asked questions

Does the Pinnacle API support player props?

Yes, through pinnapi. They are treated as special markets and returned when you pass include_specials=1 on the markets or prematch endpoints.

Why don't my player props show up by default?

Specials are opt-in. Without include_specials=1 you get the standard markets only (money line, spreads, totals, team totals).

Do player props trigger odds-drop alerts?

No. The docs are explicit: specials do not fire drop alerts. Track prop moves by polling and diffing snapshots yourself.

How do I match a prop back to its game?

Every record carries a parent_id. It is null on a main match and set to the parent's event_id on a special, so build a lookup of main events by event_id and join on it.

Which sports have player props on Pinnacle?

The family label varies by sport: baseball and basketball surface Player Props, soccer surfaces Team Props, and exact scores and futures come through the same specials flag. Coverage is thinner and lower-limit than a US retail book.

Do I need a Pinnacle account?

No. pinnapi is independent and not affiliated with Pinnacle; your pinnapi key is all you need.

Get real-time Pinnacle odds in your code

Live & prematch markets with sub-second odds-drop alerts. Free trial key in seconds — no card.

Start free trial