PinBook Odds API - Complete Tutorial
Table of Contents
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:
- Select a pricing plan
- 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
| Provider | Base URL |
|---|---|
| HugeAPI | https://pinnacle-odds-api.hgapi.top |
| RapidAPI | https://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_KEYRapidAPI Authentication
Include both headers in your request:
x-rapidapi-key: YOUR_API_KEY
x-rapidapi-host: pinbook-odds.p.rapidapi.comAPI 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
{
"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
[
{
"id": 1,
"p_id": 29,
"name": "Soccer",
"last": 1649534641,
"special_last": 1680610842,
"last_call": 1680610842
}
]Response Fields:
| Field | Type | Description |
|---|---|---|
id | integer | Sport ID in PinBook API |
p_id | integer | Sport ID in Pinnacle API |
name | string | Sport name |
last | integer | Last update timestamp for markets (Unix) |
special_last | integer | Last update timestamp for special markets (Unix) |
last_call | integer | Last update timestamp for general data (Unix) |
List of Periods
Get available betting periods for a specific sport.
Endpoint: GET /kit/v1/meta-periods
Parameters:
| Name | Type | Required | Description | Example |
|---|---|---|---|---|
sport_id | integer | Yes | Sport ID (1-29) | 1 |
Response: 200 OK
Returns period definitions like:
num_0- Match (Full Time)num_1- 1st Halfnum_2- 2nd Half- etc. (varies by sport)
Example Request:
GET /kit/v1/meta-periods?sport_id=1List of Leagues
Get all leagues for a specific sport.
Endpoint: GET /kit/v1/leagues
Parameters:
| Name | Type | Required | Description | Example |
|---|---|---|---|---|
sport_id | integer | Yes | Sport ID (1-29) | 1 |
Response: 200 OK
[
{
"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:
| Name | Type | Required | Description | Example |
|---|---|---|---|---|
sport_id | integer | Yes | Sport ID (1-29) | 5 |
since | integer | No | UTC timestamp for delta updates | 1658948800 |
league_ids | string | No | Comma-separated league IDs | "3,67,90" |
event_ids | string | No | Comma-separated event IDs | "3,67,90" |
event_type | string | No | Event type: prematch or live | "prematch" |
is_have_odds | boolean | No | Filter events with odds (alias: is_have_periods) | true |
is_have_periods | boolean | No | Filter events with periods | true |
Rate Limit: Maximum 25 requests without since parameter per 5 minutes.
Response: 200 OK
{
"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:
| Field | Type | Description |
|---|---|---|
event_id | integer | Unique event identifier |
sport_id | integer | Sport ID |
league_id | integer | League ID |
league_name | string | League name |
starts | string | Event start time (UTC) |
last | integer | Last modified timestamp |
home | string | Home team name |
away | string | Away team name |
event_type | string | prematch or live |
live_status_id | integer | 0=No live, 1=Live event, 2=Live on different event |
parent_id | integer | Parent event ID (live links to prematch) |
resulting_unit | string | Settlement basis (Regular, Corners, Bookings) |
is_actual | boolean | Event is upcoming or recently finished |
is_have_odds | boolean | Event has odds available |
is_have_periods | boolean | Event has betting periods |
is_have_open_markets | boolean | Event has open markets |
Period Fields:
| Field | Type | Description |
|---|---|---|
line_id | integer | Line identifier |
number | integer | Period number |
description | string | Period name |
cutoff | string | Betting cutoff time (UTC) |
period_status | integer | 1=Open for betting, 2=Closed |
Market Types in Periods:
| Market | Description |
|---|---|
money_line | 1X2 betting (home, draw, away odds) |
spreads | Handicap betting with points |
totals | Over/Under betting |
team_total | Team-specific totals |
List of Special Markets
Get special markets (Player Props, Futures, etc.).
Endpoint: GET /kit/v1/special-markets
Parameters:
| Name | Type | Required | Description | Example |
|---|---|---|---|---|
sport_id | integer | Yes | Sport ID (1-29) | 1 |
since | integer | No | UTC timestamp for delta updates | 1658948800 |
league_ids | string | No | Comma-separated league IDs | "3,67,90" |
event_ids | string | No | Comma-separated event IDs | "3,67,90" |
event_type | string | No | prematch or live | "prematch" |
is_have_odds | boolean | No | Filter markets with odds (alias: is_have_lines) | true |
is_have_lines | boolean | No | Filter markets with lines | true |
Response: 200 OK
{
"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:
| Field | Type | Description |
|---|---|---|
special_id | integer | Unique special market identifier |
bet_type | string | MULTI_WAY_HEAD_TO_HEAD, SPREAD, OVER_UNDER |
units | string | Measurement unit (e.g., goals for hockey) |
name | string | Special market name |
category | string | Category (Team Props, Player Props, etc.) |
status | string | O=Open, H=Halted, I=Reduced limits |
Event Details
Get detailed event information with historical odds.
Endpoint: GET /kit/v1/details
Parameters:
| Name | Type | Required | Description | Example |
|---|---|---|---|---|
event_id | integer | Yes | Event ID | 1419211461 |
Response: 200 OK
Returns event details including:
- Full event information
- Historical odds (timestamp, price, max bet)
- Period results (if settled)
Period Results Status:
| Status | Description |
|---|---|
| 1 | Event period is settled |
| 2 | Event period is re-settled |
| 3 | Event period is cancelled |
| 4 | Event period is re-settled as cancelled |
| 5 | Event is deleted |
Archive Events
Get list of past/settled events.
Endpoint: GET /kit/v1/archive
Parameters:
| Name | Type | Required | Description | Example |
|---|---|---|---|---|
sport_id | integer | Yes | Sport ID (1-29) | 1 |
page_num | integer | Yes | Page number (1-1000) | 1 |
league_ids | string | No | Comma-separated league IDs | "3,67,90" |
start_after | string | No | Filter events starting after (ISO 8601) | "2024-01-01T00:00:00" |
start_before | string | No | Filter events starting before (ISO 8601) | "2024-12-31T23:59:59" |
Response: 200 OK
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | API Sport ID |
p_id | integer | No | Pinnacle Sport ID |
name | string | No | Sport name |
last | integer | No | Last market update (Unix timestamp) |
special_last | integer | No | Last special market update (Unix timestamp) |
last_call | integer | No | Last general data update (Unix timestamp) |
MoneyLine
| Field | Type | Required | Description |
|---|---|---|---|
home | number | Yes | Home team odds |
draw | number | Yes | Draw odds |
away | number | Yes | Away team odds |
Spread
| Field | Type | Required | Description |
|---|---|---|---|
hdp | number | Yes | Handicap value |
alt_line_id | integer | No | Alternative line ID (if applicable) |
home | number | Yes | Home odds |
away | number | Yes | Away odds |
max | number | No | Maximum bet amount |
Total
| Field | Type | Required | Description |
|---|---|---|---|
points | number | Yes | Total points |
alt_line_id | integer | No | Alternative line ID |
over | number | Yes | Over odds |
under | number | Yes | Under odds |
max | number | No | Maximum bet amount |
MarketsPeriod
| Field | Type | Required | Description |
|---|---|---|---|
line_id | integer | Yes | Line identifier |
number | integer | Yes | Period number |
cutoff | string | Yes | Betting cutoff (UTC) |
period_status | integer | Yes | 1=Open, 2=Closed |
money_line | object | Yes | Money line odds |
spreads | object | Yes | Spread odds |
totals | object | Yes | Totals odds |
history | object | No | Historical odds data |
ArchiveEventPeriodResult
| Field | Type | Required | Description |
|---|---|---|---|
number | integer | Yes | Period number |
status | integer | Yes | Settlement status (1-5) |
settlement_id | integer | Yes | Unique settlement ID |
settled_at | string | Yes | Settlement timestamp (UTC) |
team_1_score | integer | Yes | Team 1 final score |
team_2_score | integer | Yes | Team 2 final score |
HTTPValidationError
| Field | Type | Required | Description |
|---|---|---|---|
detail | array | No | Array of validation errors |
ValidationError
| Field | Type | Required | Description |
|---|---|---|---|
loc | array | Yes | Error location path |
msg | string | Yes | Error message |
type | string | Yes | Error type |
Code Examples
Python (HugeAPI)
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)
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
$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)
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:
Initial Call: Make first request WITHOUT
sinceparameterGET /kit/v1/markets?sport_id=1Get Timestamp: Extract the
lastvalue from responsejson{ "last": 1658948800, ... }Delta Updates: Use
lastassincein subsequent callsGET /kit/v1/markets?sport_id=1&since=1658948800Continue Loop: Always use
sinceafter 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:
"meta": {
"open_money_line": true,
"open_spreads": true,
"open_totals": true,
"open_team_total": false
}Alternative method - all must be true:
period_status= 1- Period has odds
cutofftime is in the future
Handling Duplicates
If duplicate parent events exist (due to incorrect initial data):
- Use the event with the greater
event_id - Monitor the archive endpoint for deleted/settled events
- 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 Halfnum_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_resultsfor settlement statuscutoffvalues 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 bettingH= Temporarily haltedI= Reduced betting limits
Period Results:
- 1 = Settled
- 2 = Re-settled
- 3 = Cancelled
- 4 = Re-settled as cancelled
- 5 = Deleted
Support
- Email: tipsters@rapi.one
- Telegram: https://t.me/api_tipsters
- Other Sport APIs: https://rapi.one
- Other Books: https://oddsfe.com/
- OpenAPI Specification: https://hugeapi.com/collection/pinnacle-odds
