Skip to content

PinBook Odds API - Complete Tutorial

Table of Contents

  1. Introduction
  2. Getting Started
  3. Authentication
  4. API Endpoints
  5. Data Models
  6. Code Examples
  7. FAQ

Introduction

PinBook Odds API is a RESTful service for getting pre-match and live odds from Pinnacle. It provides real-time data updates without delay, historical odds, scores, and results.

Supported Sports

Traditional Sports:

  • Soccer, Tennis, Basketball, Hockey, American football, MMA, Baseball, Handball, Volleyball, Cricket

Esports:

  • CS2 (CS:GO), Dota 2, LoL (League of Legends), Valorant, Rocket League

Key Features

  • Real-time odds updates without delay
  • Pre-match and live odds
  • Historical odds, scores, and results
  • Special markets support (Player Props, Futures, Corners, etc.)

Getting Started

Prerequisites

Before using the API, you need to:

  1. Select a pricing plan
  2. Obtain an API key for authentication

Connection Options

Option 1: HugeAPI

  • OpenAPI Spec support
  • Crypto payment available
  • Unlimited requests
  • Unlimited bandwidth

Connect: HugeAPI Portal

Option 2: RapidAPI

  • Free base plan (550 requests/month)
  • Card payment
  • Access via RapidAPI hub

Connect: RapidAPI Connect

Base URL

ProviderBase URL
HugeAPIhttps://pinnacle-odds-api.hgapi.top
RapidAPIhttps://pinbook-odds.p.rapidapi.com

Authentication

All API requests require authentication via API key.

HugeAPI Authentication

Include your API key in the request header:

x-portal-apikey: YOUR_API_KEY

RapidAPI Authentication

Include both headers in your request:

x-rapidapi-key: YOUR_API_KEY
x-rapidapi-host: pinbook-odds.p.rapidapi.com

API Endpoints

Betting Status

Check the Pinnacle server betting status.

Endpoint: GET /kit/v1/betting-status

Description: Get current betting status to verify Pinnacle server availability.

Response: 200 OK

json
{
  "status": "online"
}

List of Sports

Get a list of all available sports.

Endpoint: GET /kit/v1/sports

Description: Retrieve all supported sports with their IDs and metadata.

Response: 200 OK

json
[
  {
    "id": 1,
    "p_id": 29,
    "name": "Soccer",
    "last": 1649534641,
    "special_last": 1680610842,
    "last_call": 1680610842
  }
]

Response Fields:

FieldTypeDescription
idintegerSport ID in PinBook API
p_idintegerSport ID in Pinnacle API
namestringSport name
lastintegerLast update timestamp for markets (Unix)
special_lastintegerLast update timestamp for special markets (Unix)
last_callintegerLast update timestamp for general data (Unix)

List of Periods

Get available betting periods for a specific sport.

Endpoint: GET /kit/v1/meta-periods

Parameters:

NameTypeRequiredDescriptionExample
sport_idintegerYesSport ID (1-29)1

Response: 200 OK

Returns period definitions like:

  • num_0 - Match (Full Time)
  • num_1 - 1st Half
  • num_2 - 2nd Half
  • etc. (varies by sport)

Example Request:

GET /kit/v1/meta-periods?sport_id=1

List of Leagues

Get all leagues for a specific sport.

Endpoint: GET /kit/v1/leagues

Parameters:

NameTypeRequiredDescriptionExample
sport_idintegerYesSport ID (1-29)1

Response: 200 OK

json
[
  {
    "id": 1842,
    "name": "Germany - Bundesliga",
    "home_team_type": "Team1"
  }
]

Important: Use home_team_type to determine which team is home:

  • "Team1" - First team is home
  • "Team2" - Second team is home

List of Markets

Get odds and market data for events.

Endpoint: GET /kit/v1/markets

Description: Retrieve market data with odds. Always start with a snapshot call (without since), then continue with delta calls using the since parameter.

Parameters:

NameTypeRequiredDescriptionExample
sport_idintegerYesSport ID (1-29)5
sinceintegerNoUTC timestamp for delta updates1658948800
league_idsstringNoComma-separated league IDs"3,67,90"
event_idsstringNoComma-separated event IDs"3,67,90"
event_typestringNoEvent type: prematch or live"prematch"
is_have_oddsbooleanNoFilter events with odds (alias: is_have_periods)true
is_have_periodsbooleanNoFilter events with periodstrue

Rate Limit: Maximum 25 requests without since parameter per 5 minutes.

Response: 200 OK

json
{
  "sport_id": 1,
  "sport_name": "Soccer",
  "last": 1745671020,
  "last_call": 1745671020,
  "events": [
    {
      "event_id": 1607709101,
      "sport_id": 1,
      "league_id": 1842,
      "league_name": "Germany - Bundesliga",
      "starts": "2025-04-26T13:30:00",
      "last": 1745671014,
      "home": "Bayern Munich",
      "away": "Mainz 05",
      "event_type": "prematch",
      "live_status_id": 2,
      "parent_id": null,
      "resulting_unit": "Regular",
      "is_actual": true,
      "is_have_odds": true,
      "is_have_periods": true,
      "is_have_open_markets": true,
      "periods": {
        "num_0": {
          "line_id": 3080353239,
          "number": 0,
          "description": "Match",
          "cutoff": "2025-04-26T13:30:00",
          "period_status": 1,
          "money_line": {
            "home": 1.228,
            "draw": 7.38,
            "away": 11.4
          },
          "spreads": {
            "-2.0": {
              "hdp": -2.0,
              "alt_line_id": null,
              "home": 1.99,
              "away": 1.917,
              "max": 10000.0
            }
          },
          "totals": {
            "3.75": {
              "points": 3.75,
              "alt_line_id": null,
              "over": 1.877,
              "under": 2.02,
              "max": 5000.0
            }
          },
          "team_total": {
            "home": {
              "points": 2.5,
              "over": 1.636,
              "under": 2.31
            },
            "away": {
              "points": 0.5,
              "over": 1.558,
              "under": 2.49
            }
          },
          "meta": {
            "number": 0,
            "max_spread": 10000.0,
            "max_money_line": 10000.0,
            "max_total": 5000.0,
            "max_team_total": 1500.0,
            "open_money_line": true,
            "open_spreads": true,
            "open_totals": true,
            "open_team_total": true
          }
        }
      }
    }
  ]
}

Event Fields:

FieldTypeDescription
event_idintegerUnique event identifier
sport_idintegerSport ID
league_idintegerLeague ID
league_namestringLeague name
startsstringEvent start time (UTC)
lastintegerLast modified timestamp
homestringHome team name
awaystringAway team name
event_typestringprematch or live
live_status_idinteger0=No live, 1=Live event, 2=Live on different event
parent_idintegerParent event ID (live links to prematch)
resulting_unitstringSettlement basis (Regular, Corners, Bookings)
is_actualbooleanEvent is upcoming or recently finished
is_have_oddsbooleanEvent has odds available
is_have_periodsbooleanEvent has betting periods
is_have_open_marketsbooleanEvent has open markets

Period Fields:

FieldTypeDescription
line_idintegerLine identifier
numberintegerPeriod number
descriptionstringPeriod name
cutoffstringBetting cutoff time (UTC)
period_statusinteger1=Open for betting, 2=Closed

Market Types in Periods:

MarketDescription
money_line1X2 betting (home, draw, away odds)
spreadsHandicap betting with points
totalsOver/Under betting
team_totalTeam-specific totals

List of Special Markets

Get special markets (Player Props, Futures, etc.).

Endpoint: GET /kit/v1/special-markets

Parameters:

NameTypeRequiredDescriptionExample
sport_idintegerYesSport ID (1-29)1
sinceintegerNoUTC timestamp for delta updates1658948800
league_idsstringNoComma-separated league IDs"3,67,90"
event_idsstringNoComma-separated event IDs"3,67,90"
event_typestringNoprematch or live"prematch"
is_have_oddsbooleanNoFilter markets with odds (alias: is_have_lines)true
is_have_linesbooleanNoFilter markets with linestrue

Response: 200 OK

json
{
  "sport_id": 1,
  "sport_name": "Soccer",
  "last": 1745671020,
  "last_call": 1745671020,
  "specials": [
    {
      "special_id": 1608338379,
      "sport_id": 1,
      "league_id": 1980,
      "event_id": 1607803934,
      "last": 1745732750,
      "live_status": "prematch",
      "live_status_id": 2,
      "bet_type": "MULTI_WAY_HEAD_TO_HEAD",
      "units": null,
      "name": "3-Way Handicap Bournemouth -2",
      "starts": "2025-04-27T13:00:00",
      "cutoff": "2025-04-27T13:00:00",
      "category": "Team Props",
      "status": "O",
      "event": {
        "id": 1607803934,
        "period_number": 0,
        "home": "Bournemouth",
        "away": "Manchester United"
      },
      "is_actual": true,
      "max_bet": 500.0,
      "is_have_odds": true,
      "is_have_lines": true,
      "open": true,
      "lines": {
        "c_1608338380": {
          "id": 1608338380,
          "name": "Bournemouth (-2)",
          "rot_num": 2824,
          "line_id": 4941359540,
          "price": 5.93,
          "handicap": null
        }
      }
    }
  ]
}

Special Market Fields:

FieldTypeDescription
special_idintegerUnique special market identifier
bet_typestringMULTI_WAY_HEAD_TO_HEAD, SPREAD, OVER_UNDER
unitsstringMeasurement unit (e.g., goals for hockey)
namestringSpecial market name
categorystringCategory (Team Props, Player Props, etc.)
statusstringO=Open, H=Halted, I=Reduced limits

Event Details

Get detailed event information with historical odds.

Endpoint: GET /kit/v1/details

Parameters:

NameTypeRequiredDescriptionExample
event_idintegerYesEvent ID1419211461

Response: 200 OK

Returns event details including:

  • Full event information
  • Historical odds (timestamp, price, max bet)
  • Period results (if settled)

Period Results Status:

StatusDescription
1Event period is settled
2Event period is re-settled
3Event period is cancelled
4Event period is re-settled as cancelled
5Event is deleted

Archive Events

Get list of past/settled events.

Endpoint: GET /kit/v1/archive

Parameters:

NameTypeRequiredDescriptionExample
sport_idintegerYesSport ID (1-29)1
page_numintegerYesPage number (1-1000)1
league_idsstringNoComma-separated league IDs"3,67,90"
start_afterstringNoFilter events starting after (ISO 8601)"2024-01-01T00:00:00"
start_beforestringNoFilter events starting before (ISO 8601)"2024-12-31T23:59:59"

Response: 200 OK

json
{
  "sport_id": 1,
  "sport_name": "Soccer",
  "events": [
    {
      "event_id": 1581573400,
      "sport_id": 1,
      "league_id": 1863,
      "league_name": "Club Friendlies",
      "starts": "2023-11-05T18:00:00",
      "home": "Deportes Concepcion",
      "away": "Nublense",
      "event_type": "live",
      "parent_id": 1581570826,
      "resulting_unit": "Regular",
      "is_have_odds": true,
      "periods": [...],
      "period_results": [
        {
          "number": 0,
          "status": 1,
          "settlement_id": 15169390,
          "settled_at": "2023-11-05T18:54:55.05Z",
          "team_1_score": 3,
          "team_2_score": 0
        }
      ]
    }
  ]
}

Data Models

Sport

FieldTypeRequiredDescription
idintegerYesAPI Sport ID
p_idintegerNoPinnacle Sport ID
namestringNoSport name
lastintegerNoLast market update (Unix timestamp)
special_lastintegerNoLast special market update (Unix timestamp)
last_callintegerNoLast general data update (Unix timestamp)

MoneyLine

FieldTypeRequiredDescription
homenumberYesHome team odds
drawnumberYesDraw odds
awaynumberYesAway team odds

Spread

FieldTypeRequiredDescription
hdpnumberYesHandicap value
alt_line_idintegerNoAlternative line ID (if applicable)
homenumberYesHome odds
awaynumberYesAway odds
maxnumberNoMaximum bet amount

Total

FieldTypeRequiredDescription
pointsnumberYesTotal points
alt_line_idintegerNoAlternative line ID
overnumberYesOver odds
undernumberYesUnder odds
maxnumberNoMaximum bet amount

MarketsPeriod

FieldTypeRequiredDescription
line_idintegerYesLine identifier
numberintegerYesPeriod number
cutoffstringYesBetting cutoff (UTC)
period_statusintegerYes1=Open, 2=Closed
money_lineobjectYesMoney line odds
spreadsobjectYesSpread odds
totalsobjectYesTotals odds
historyobjectNoHistorical odds data

ArchiveEventPeriodResult

FieldTypeRequiredDescription
numberintegerYesPeriod number
statusintegerYesSettlement status (1-5)
settlement_idintegerYesUnique settlement ID
settled_atstringYesSettlement timestamp (UTC)
team_1_scoreintegerYesTeam 1 final score
team_2_scoreintegerYesTeam 2 final score

HTTPValidationError

FieldTypeRequiredDescription
detailarrayNoArray of validation errors

ValidationError

FieldTypeRequiredDescription
locarrayYesError location path
msgstringYesError message
typestringYesError type

Code Examples

Python (HugeAPI)

python
import requests
import json
import time

headers = {
    "x-portal-apikey": "__YOUR_KEY__",
}

base_url = "https://pinnacle-odds-api.hgapi.top"
s = requests.Session()
s.headers.update(headers)

since_items = {}
all_events = {}

while True:
    for sport_id in [1]:  # endpoint /kit/v1/sports
        # is_have_odds = 0 or 1, event_type = prematch, live
        params = {
            'sport_id': sport_id,
            'is_have_odds': True,
            'since': since_items.get(sport_id)
        }
        print('[REQUEST] %s' % params)

        response = s.get(base_url + '/kit/v1/markets', params=params)
        if response.status_code != 200:
            raise Exception(response.status_code, response.text)

        result = json.loads(response.text)
        since_items[sport_id] = result['last']

        for event in result['events']:
            all_events[str(event['event_id'])] = event

            try:
                print('   %s: %s%s %s' % (
                    event['league_name'],
                    event['home'],
                    event['away'],
                    event['periods']['num_0']['money_line']
                ))
            except KeyError:
                pass

        print('Sport: %s' % result['sport_name'])
        print('Number of changes: %s' % len(result['events']))
        print(' ')

        time.sleep(3)

Python (RapidAPI)

python
import requests
import json
import time

headers = {
    "x-rapidapi-key": "__YOUR_KEY__",
    "x-rapidapi-host": "pinbook-odds.p.rapidapi.com"
}

base_url = "https://pinbook-odds.p.rapidapi.com"
s = requests.Session()
s.headers.update(headers)

since_items = {}
all_events = {}

while True:
    for sport_id in [1]:
        params = {
            'sport_id': sport_id,
            'is_have_odds': True,
            'since': since_items.get(sport_id)
        }
        print('[REQUEST] %s' % params)

        response = s.get(base_url + '/kit/v1/markets', params=params)
        if response.status_code != 200:
            raise Exception(response.status_code, response.text)

        result = json.loads(response.text)
        since_items[sport_id] = result['last']

        for event in result['events']:
            all_events[str(event['event_id'])] = event

        time.sleep(3)

PHP (RapidAPI)

php
<?php

$headers = [];
$headers[] = "x-rapidapi-key: __YOUR_KEY__";
$headers[] = "x-rapidapi-host: pinbook-odds.p.rapidapi.com";

$baseUrl = "https://pinbook-odds.p.rapidapi.com";
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_ENCODING, "");

$sinceItems = [];
$allEvents = [];

while (true) {
    foreach ([1] as $sportId) {
        $params = [];
        $params['sport_id'] = $sportId;
        $params['is_have_odds'] = true;
        $params['since'] = $sinceItems[$sportId] ?? null;

        $queryString = http_build_query($params);
        echo '[REQUEST] ' . $queryString . PHP_EOL;

        curl_setopt($ch, CURLOPT_URL, $baseUrl . '/kit/v1/markets?' . $queryString);

        $response = curl_exec($ch);
        $result = json_decode($response, true);

        $sinceItems[$sportId] = $result['last'];

        foreach ($result['events'] as $event) {
            $allEvents[(string)$event['event_id']] = $event;
        }

        sleep(3);
    }
}

curl_close($ch);

JavaScript (Node.js)

javascript
const axios = require('axios');

const headers = {
    "x-rapidapi-key": "__YOUR_KEY__",
    "x-rapidapi-host": "pinbook-odds.p.rapidapi.com"
};

const baseUrl = "https://pinbook-odds.p.rapidapi.com";
const axiosInstance = axios.create({ headers });

let sinceItems = {};
let allEvents = {};

const fetchData = async () => {
    while (true) {
        for (const sportId of [1]) {
            const params = {
                sport_id: sportId,
                is_have_odds: true,
                since: sinceItems[sportId]
            };
            console.log('[REQUEST]', params);

            try {
                const response = await axiosInstance.get(`${baseUrl}/kit/v1/markets`, { params });
                const result = response.data;
                sinceItems[sportId] = result.last;

                for (const event of result.events) {
                    allEvents[String(event.event_id)] = event;
                }

                console.log('Sport:', result.sport_name);
                console.log('Number of changes:', result.events.length);

                await new Promise(resolve => setTimeout(resolve, 3000));
            } catch (error) {
                console.error(error);
                await new Promise(resolve => setTimeout(resolve, 3000));
            }
        }
    }
};

fetchData();

Using the since Parameter

The since parameter is crucial for efficient data fetching:

  1. Initial Call: Make first request WITHOUT since parameter

    GET /kit/v1/markets?sport_id=1
  2. Get Timestamp: Extract the last value from response

    json
    { "last": 1658948800, ... }
  3. Delta Updates: Use last as since in subsequent calls

    GET /kit/v1/markets?sport_id=1&since=1658948800
  4. Continue Loop: Always use since after the first call to avoid rate limits

Rate Limit: Maximum 25 requests without since per 5 minutes.

Determining Open Markets

Check if a market is open for betting using the meta object:

json
"meta": {
    "open_money_line": true,
    "open_spreads": true,
    "open_totals": true,
    "open_team_total": false
}

Alternative method - all must be true:

  1. period_status = 1
  2. Period has odds
  3. cutoff time is in the future

Handling Duplicates

If duplicate parent events exist (due to incorrect initial data):

  1. Use the event with the greater event_id
  2. Monitor the archive endpoint for deleted/settled events
  3. Discard events marked as deleted or settled

Timezone

All timestamps in the API are in GMT/UTC (timezone 0).


FAQ

Is there data delay?

No. The API provides data without delay. Note that Pinnacle's website shows delayed data if you're not logged in.

What's the difference between prematch and live?

Prematch and live events are separate entities with different event_id values. Live events have their prematch parent in parent_id.

How do I know which team is home?

Use the /kit/v1/leagues endpoint to check home_team_type:

  • "Team1" = First team listed is home
  • "Team2" = Second team listed is home

What do period numbers mean?

Use /kit/v1/meta-periods?sport_id=X to get period definitions:

  • num_0 = Match (Full Time)
  • num_1 = 1st Half
  • num_2 = 2nd Half
  • (varies by sport)

How do I know if an event is finished?

Use /kit/v1/archive or /kit/v1/details endpoints to check:

  • period_results for settlement status
  • cutoff values for timing

What are the status codes?

Period Status:

  • 1 = Online, open for betting
  • 2 = Offline, not open for betting

Special Status:

  • O = Open for betting
  • H = Temporarily halted
  • I = Reduced betting limits

Period Results:

  • 1 = Settled
  • 2 = Re-settled
  • 3 = Cancelled
  • 4 = Re-settled as cancelled
  • 5 = Deleted

Support

We’re dedicated to providing the best API products