{
  "info": {
    "_postman_id": "b7e6c1a2-4f4e-4d2c-9a1b-pinnapi000001",
    "name": "pinnapi — Real-Time Pinnacle Odds API",
    "description": "# pinnapi — Real-Time Pinnacle Odds API\n\n**pinnapi is a real-time Pinnacle odds API**: live and prematch markets from the sharpest bookmaker in the world, pushed to your code over REST, SSE, and WebSocket in roughly **15–40 ms** — with server-side odds-drop alerts and no-vig fair prices built in.\n\nPinnacle closed its public API on **July 23, 2025**. pinnapi is an independent, drop-in replacement served from its own real-time feed: same familiar `events → periods → markets` response shape, no Pinnacle account required — your API key is your login.\n\n🔑 **Free key in seconds (no card):** [pinnapi.com](https://pinnapi.com) · 📖 **Full reference:** [pinnapi.com/docs](https://pinnapi.com/docs)\n\n---\n\n## Why the Pinnacle odds API specifically\n\nPinnacle runs a low-margin (~2% overround), high-limit book that welcomes winners — so its line is the market's **reference price**, the one soft bookmakers follow with a measurable lag. If you're building value betting, arbitrage detection, +EV scanning, steam-move tracking, or model calibration, this is the line everything gets compared against. [Why Pinnacle is the sharpest bookmaker →](https://pinnapi.com/blog/why-pinnacle-is-the-sharpest-bookmaker)\n\n## Quick start\n\n1. Get a free key at [pinnapi.com](https://pinnapi.com) (100 REST requests/day, no card).\n2. Set the `apiKey` collection variable.\n3. Run **Live odds → Markets**:\n\n```bash\ncurl -H \"x-portal-apikey: $KEY\" \\\n  \"https://pinnapi.com/kit/v1/markets?sport_id=1&event_type=live\"\n```\n\nEvery REST endpoint authenticates with the `x-portal-apikey` header (configured collection-wide here). The SSE streams authenticate via `?key=` in the URL.\n\n## What's in this collection\n\n| Folder | Endpoints | What you get |\n| --- | --- | --- |\n| **Live odds** | `/kit/v1/markets`, `/kit/v1/details` | Full in-play board per sport: moneyline, spreads, totals, team totals, every period, plus live `state` (score/clock) |\n| **Prematch odds** | `/kit/v1/prematch/fixtures`, `/markets`, `/lines` | Pre-game lines with the same envelope; `since` param for incremental polling |\n| **Dropping odds** | `/api/drops`, SSE `/odds-drop`, SSE `/odds-drop-prematch` | Queryable buffer of recent drops + push streams that fire the instant a price falls past **your** threshold |\n| **Status** | `/health`, `/ping` | Auth check and liveness probe |\n\n## Sport IDs\n\n| ID | Sport | ID | Sport |\n| --- | --- | --- | --- |\n| 1 | Soccer | 7 | Rugby |\n| 2 | Tennis | 8 | MMA |\n| 3 | Basketball | 9 | Boxing |\n| 4 | Hockey | 10 | Volleyball/Handball |\n| 5 | American Football | 11 | Esports |\n| 6 | Baseball | 12 | Golf |\n\n## The drop streams (the part people pay for)\n\nThe SSE endpoints hold one connection open and push every Pinnacle odds drop past your `min_drop` percentage the moment it fires — no polling loop, no missed windows. Each alert carries `from_price`, `to_price`, and `nvp` (the **no-vig fair price**, computed server-side) so the fair-value half of any +EV check arrives precomputed. Latency is ~15–40 ms end-to-end, with a [published, reproducible methodology](https://pinnapi.com/blog/pinnacle-api-latency-benchmark). Streams require a paid plan (from $99/mo); the free tier is REST-only.\n\n## SDKs\n\n- **Python:** `pip install pinnapi` — [PyPI](https://pypi.org/project/pinnapi/)\n- **Node.js / TypeScript:** `npm install pinnapi` — [npm](https://www.npmjs.com/package/pinnapi)\n- **R:** `pinnapiR` — tidy data frames for the sports-analytics crowd\n\n## How it compares\n\nHonest, long-form comparisons with the alternatives: [The Odds API](https://pinnapi.com/blog/pinnapi-vs-the-odds-api) · [OpticOdds](https://pinnapi.com/blog/pinnapi-vs-opticodds) · [OddsJam](https://pinnapi.com/blog/pinnapi-vs-oddsjam) · [BetsAPI](https://pinnapi.com/blog/pinnapi-vs-betsapi) · [SharpAPI](https://pinnapi.com/blog/pinnapi-vs-sharpapi) · [OddsPapi](https://pinnapi.com/blog/pinnapi-vs-oddspapi) — each one explicit about where the other product wins.\n\n## Pricing\n\nFree tier: **100 REST requests/day, no card**. Paid plans **$99–$229/month**, published, self-serve — no sales call. [Plans →](https://pinnapi.com/#pricing)\n\n---\n\n*pinnapi is an independent service, **not affiliated with or endorsed by Pinnacle**. Real-time feed only (live + prematch) — no historical archive.*",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "x-portal-apikey",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{apiKey}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://pinnapi.com",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string",
      "description": "Your pinnapi API key — free at https://pinnapi.com (no card)"
    }
  ],
  "item": [
    {
      "name": "Live odds",
      "item": [
        {
          "name": "Markets (live board for a sport)",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/kit/v1/markets?sport_id=1&event_type=live",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "kit",
                "v1",
                "markets"
              ],
              "query": [
                {
                  "key": "sport_id",
                  "value": "1",
                  "description": "Required. 1=Soccer, 2=Tennis, 3=Basketball… (see collection description)"
                },
                {
                  "key": "event_type",
                  "value": "live",
                  "description": "live (default) | prematch — same envelope either way"
                },
                {
                  "key": "since",
                  "value": "",
                  "description": "Optional: only events changed since this `last` version (incremental polling)",
                  "disabled": true
                }
              ]
            },
            "description": "Every event for a sport with its full market tree — moneyline, spreads, totals, team totals — across all periods (`periods.num_0` = full match, `num_1` = first half…). Response envelope: `{ sport_id, sport_name, last, last_call, events: [...] }`. Live events include a `state` object (score, clock, cards).\n\nDocs: https://pinnapi.com/docs#markets"
          },
          "response": []
        },
        {
          "name": "Details (one event by ID)",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/kit/v1/details?event_id=1628594960",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "kit",
                "v1",
                "details"
              ],
              "query": [
                {
                  "key": "event_id",
                  "value": "1628594960",
                  "description": "Required. Event ID from a previous /markets response"
                }
              ]
            },
            "description": "One live event, same per-event shape as `/kit/v1/markets`.\n\nDocs: https://pinnapi.com/docs#details"
          },
          "response": []
        }
      ]
    },
    {
      "name": "Prematch odds",
      "item": [
        {
          "name": "Prematch fixtures (all upcoming, with markets)",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/kit/v1/prematch/fixtures?sport_id=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "kit",
                "v1",
                "prematch",
                "fixtures"
              ],
              "query": [
                {
                  "key": "sport_id",
                  "value": "1",
                  "description": "Required. Sport ID"
                },
                {
                  "key": "since",
                  "value": "",
                  "description": "Optional: incremental polling on the `last` version counter",
                  "disabled": true
                }
              ]
            },
            "description": "All pre-game fixtures for a sport with the complete markets-and-periods tree. Identical shape to `/kit/v1/markets`; only `event_type` differs. The same `event_id` carries from prematch into live — nothing re-keys at kickoff.\n\nDocs: https://pinnapi.com/docs#prematch-fixtures"
          },
          "response": []
        },
        {
          "name": "Prematch markets (one event)",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/kit/v1/prematch/markets?event_id=1628594960",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "kit",
                "v1",
                "prematch",
                "markets"
              ],
              "query": [
                {
                  "key": "event_id",
                  "value": "1628594960",
                  "description": "Required"
                }
              ]
            },
            "description": "Markets for a single prematch event.\n\nDocs: https://pinnapi.com/docs#prematch-markets"
          },
          "response": []
        },
        {
          "name": "Prematch lines (compact view)",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/kit/v1/prematch/lines?event_id=1628594960",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "kit",
                "v1",
                "prematch",
                "lines"
              ],
              "query": [
                {
                  "key": "event_id",
                  "value": "1628594960",
                  "description": "Required"
                }
              ]
            },
            "description": "Compact line view for one prematch event.\n\nDocs: https://pinnapi.com/docs#prematch-lines"
          },
          "response": []
        }
      ]
    },
    {
      "name": "Dropping odds",
      "item": [
        {
          "name": "Recent drops (queryable buffer)",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/api/drops?mode=live&min_drop_pct=5&max_age_sec=600",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "drops"
              ],
              "query": [
                {
                  "key": "mode",
                  "value": "live",
                  "description": "live (default) | prematch"
                },
                {
                  "key": "sport_id",
                  "value": "",
                  "description": "Optional sport filter",
                  "disabled": true
                },
                {
                  "key": "min_drop_pct",
                  "value": "5",
                  "description": "Minimum drop percent (default 5, fractional allowed)"
                },
                {
                  "key": "max_age_sec",
                  "value": "600",
                  "description": "Only alerts younger than this"
                },
                {
                  "key": "limit",
                  "value": "",
                  "description": "Max alerts returned (default 500)",
                  "disabled": true
                }
              ]
            },
            "description": "What just dropped — no open connection needed. Returns `{ total, drops: [...], meta }`; each drop carries `from_price`, `to_price`, `nvp` (no-vig fair price, computed server-side), `outcome`, `period`, timestamps and more.\n\nDocs: https://pinnapi.com/docs#api-drops"
          },
          "response": []
        },
        {
          "name": "SSE stream — live drops (push)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "text/event-stream"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/odds-drop?key={{apiKey}}&min_drop=5",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "odds-drop"
              ],
              "query": [
                {
                  "key": "key",
                  "value": "{{apiKey}}",
                  "description": "SSE auths via query param (EventSource can't set headers)"
                },
                {
                  "key": "min_drop",
                  "value": "5",
                  "description": "Drop-percent threshold for THIS subscriber (default 5, floor 1)"
                }
              ]
            },
            "description": "**Server-Sent Events** — the connection stays open; every live Pinnacle drop past your threshold is pushed the instant it fires (~15–40 ms measured end-to-end). First frame is a `{\"type\":\"connected\"}` handshake; every subsequent frame is an **array** of drop alerts. One SSE connection per account.\n\nRequires a plan with stream access (Drops / Edge / Scale).\n\nDocs: https://pinnapi.com/docs#sse-live"
          },
          "response": []
        },
        {
          "name": "SSE stream — prematch drops (push)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "text/event-stream"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/odds-drop-prematch?key={{apiKey}}&min_drop=5",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "odds-drop-prematch"
              ],
              "query": [
                {
                  "key": "key",
                  "value": "{{apiKey}}"
                },
                {
                  "key": "min_drop",
                  "value": "5",
                  "description": "Threshold percent (default 5, floor 1)"
                },
                {
                  "key": "recheck",
                  "value": "",
                  "description": "Optional stable-price filter: hold an alert N seconds, re-verify, only emit if the drop held",
                  "disabled": true
                }
              ]
            },
            "description": "Same as `/odds-drop` for pre-game markets, with an optional `?recheck=N` stable-price filter.\n\nDocs: https://pinnapi.com/docs#sse-prematch"
          },
          "response": []
        }
      ]
    },
    {
      "name": "Status",
      "item": [
        {
          "name": "Health (authed)",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/health",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "health"
              ]
            },
            "description": "Connectivity + auth check — the first call to make with a fresh key.\n\nDocs: https://pinnapi.com/docs#health"
          },
          "response": []
        },
        {
          "name": "Ping (public)",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/ping",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ping"
              ]
            },
            "description": "Unauthenticated liveness probe; returns `ok`."
          },
          "response": []
        }
      ]
    }
  ]
}
