Documentation

PulseScore API Docs

One normalized schema across every bookmaker. Same fields, same flat markets[] with canonicalMarket and selections[]. Your parser stays the same whether you call /v3/bet365, /fanduel, or any other bookmaker.

Bookmaker API References

Quick Start

Get live odds data in 3 steps:

  1. Create an account - Sign up at pulsescore.net (free BASIC plan: 500 requests/month)
  2. Generate an API key - Go to Dashboard → Generate API Key
  3. Make your first call - Use the key in the X-Secret header
Your first request
curl -X GET "https://api.pulsescore.net/api/v3/bet365/live-events?sport=soccer" \
  -H "X-Secret: YOUR_API_KEY"

Authentication

All API requests require an API key passed in the request header:

HeaderValue
X-SecretYour API key from the dashboard

API keys can be generated and revoked from your dashboard. Keep your keys secret - do not expose them in client-side code.

REST API

The REST API returns JSON in the normalized schema. Every bookmaker exposes the same endpoint paths and response shape — only the base prefix differs:

text
https://api.pulsescore.net/api/v3/bet365      # Bet365 (normalized)
https://api.pulsescore.net/api/fanduel        # Fanduel
https://api.pulsescore.net/api/bwin           # Bwin
https://api.pulsescore.net/api/ps3838         # Pinnacle(PS3838)
https://api.pulsescore.net/api/paddypower     # Paddy Power
https://api.pulsescore.net/api/{bookmaker}    # …same pattern for every bookie

The endpoints below use /api/v3/bet365 as the example base URL. Swap the prefix for any other bookmaker — the response shape is identical. Standard HTTP status codes indicate success or failure.

Schema & Fallback

Every event payload exposes a normalized layer alongside the original bookmaker labels. Use the canonical fields by default — they're stable across bookmakers and safe to switch on in your code. If a canonical value looks wrong or is missing, fall back to the bookmaker's raw label, available on both markets and selections.

Markets

FieldUseWhen to fall back
canonicalMarketNormalized market key (e.g. match_winner, total_goals).Primary identifier — switch on this.
periodNormalized period (e.g. fulltime, first_half).If period looks wrong or unmapped, read rawName.
rawNameOriginal market label from the bookmaker (e.g. "Full Time Result").Fallback when canonicalMarket or period are wrong or missing.

Selections / Outcomes

The same rule applies inside selections[]: use the normalized name, and fall back to rawName if it doesn't match what you expect.

FieldUse
nameNormalized outcome label (e.g. Over 2.5, team name).
rawNameOriginal outcome label from the bookmaker. Use as fallback when name looks wrong.

Recommended pattern: read canonicalMarket / period / name first, fall back to rawName when the normalized value is unexpected. Report mis-mapped markets to support so the normalization layer can be improved.

Endpoints

Live Events

GET/live-events

Fetch all currently live events with real-time odds and market groups.

Parameters

NameTypeRequiredDescription
sportstringNoFilter by sport: soccer (default), tennis, basketball, ice-hockey, volleyball, handball, table-tennis, esports (or e-sports — both accepted), american-football, baseball, rugby-league, rugby-union

Response

json
[
  {
    "eventId": "98734521",
    "sport": "soccer",
    "league": "Premier League",
    "home": "Liverpool",
    "away": "Man City",
    "live": true,
    "startTime": "2026-05-20T19:00:00.000Z",
    "markets": [
      {
        "canonicalMarket": "match_winner",
        "period": "fulltime",
        "selections": [
          { "name": "Liverpool", "decimal": "2.10" },
          { "name": "Draw", "decimal": "3.40" },
          { "name": "Man City", "decimal": "3.25" }
        ]
      },
      {
        "canonicalMarket": "total_goals",
        "period": "fulltime",
        "line": "2.5",
        "selections": [
          { "name": "Over 2.5", "decimal": "1.75" },
          { "name": "Under 2.5", "decimal": "2.05" }
        ]
      }
    ]
  }
]
GET/live-events/sports

Get a list of all sports that currently have live events.

Response

json
[
  { "sport": "soccer", "count": 42 },
  { "sport": "tennis", "count": 18 },
  { "sport": "basketball", "count": 8 }
]
GET/live-events/:eventId

Fetch a single live event by its eventId.

Parameters

NameTypeRequiredDescription
eventIdstringYesEvent ID from /live-events response

Response

json
{
  "eventId": "98734521",
  "sport": "soccer",
  "league": "Premier League",
  "home": "Liverpool",
  "away": "Man City",
  "live": true,
  "startTime": "2026-05-20T19:00:00.000Z",
  "markets": [ ... ]
}

Pre-Match (by Sport)

Pre-match data is organized by sport. Replace {sport} with: soccer (root), tennis, basketball, ice-hockey, american-football, horse-racing, volleyball, handball, table-tennis, esports (or e-sports — both accepted), baseball, greyhounds. Soccer uses the root path (no sport prefix).

GET/{sport}/leagues

Get all available leagues for a sport, paginated.

Parameters

NameTypeRequiredDescription
pagenumberNoPage number (default 1)
limitnumberNoItems per page (default 50)

Response

json
[
  {
    "league": "Premier League",
    "sport": "soccer",
    "live": false,
    "eventCount": 10
  }
]
GET/{sport}/leagues/:league/events

Get all events for a specific league.

Parameters

NameTypeRequiredDescription
leaguestringYesLeague name from /leagues response

Response

json
[
  {
    "eventId": "45921003",
    "sport": "soccer",
    "league": "Premier League",
    "home": "Arsenal",
    "away": "Chelsea",
    "live": false,
    "startTime": "2026-05-21T15:00:00.000Z",
    "markets": [
      {
        "canonicalMarket": "match_winner",
        "period": "fulltime",
        "selections": [
          { "name": "Arsenal", "decimal": "2.30" },
          { "name": "Draw", "decimal": "3.20" },
          { "name": "Chelsea", "decimal": "3.10" }
        ]
      }
    ]
  }
]
GET/{sport}/events

Paginated list of events for a sport.

Parameters

NameTypeRequiredDescription
pagenumberNoPage number (default 1)
limitnumberNoItems per page (default 50)

Response

json
[
  {
    "eventId": "45921003",
    "sport": "soccer",
    "league": "Premier League",
    "home": "Arsenal",
    "away": "Chelsea",
    "live": false,
    "startTime": "2026-05-21T15:00:00.000Z",
    "markets": [ ... ]
  }
]
GET/{sport}/events/:eventId

Fetch a single event by its eventId.

Parameters

NameTypeRequiredDescription
eventIdstringYesEvent ID from /events or /leagues/:league/events

Response

json
{
  "eventId": "45921003",
  "sport": "soccer",
  "league": "Premier League",
  "home": "Arsenal",
  "away": "Chelsea",
  "live": false,
  "startTime": "2026-05-21T15:00:00.000Z",
  "markets": [ ... ]
}

WebSocket

The WebSocket feed provides real-time streaming of all in-play events for a chosen bookmaker and sport. Server pushes a frame every ~1 second containing every live event for the subscribed sport. Available on PRO and MAX plans.

Connection Pattern

Every bookmaker uses the same connection pattern — only the path segment changes. Pass your API key and the desired sport as query parameters:

text
wss://api.pulsescore.net/api/{bookmaker}/ws/live?key=YOUR_API_KEY&sport=SPORT

Bet365 uses the versioned path /api/v3/bet365/ws/live; every other bookmaker follows /api/{bookmaker}/ws/live. All frames share the same normalized event schema. Full URLs in the table below.

Endpoints by Bookmaker

BookmakerURL
Bet365wss://api.pulsescore.net/api/v3/bet365/ws/live
Unibet AUwss://api.pulsescore.net/api/unibetau/ws/live
Fanduelwss://api.pulsescore.net/api/fanduel/ws/live
Bwinwss://api.pulsescore.net/api/bwin/ws/live
DraftKingswss://api.pulsescore.net/api/draftkings/ws/live
Ladbrokeswss://api.pulsescore.net/api/ladbrokes/ws/live
Paddy Powerwss://api.pulsescore.net/api/paddypower/ws/live
Betfredwss://api.pulsescore.net/api/betfred/ws/live
Betano DEwss://api.pulsescore.net/api/betano-de/ws/live
BetMGM(CO.UK)wss://api.pulsescore.net/api/betmgm-couk/ws/live
BetMGM(NL)wss://api.pulsescore.net/api/betmgm-nl/ws/live

All endpoints require ?key=YOUR_API_KEY&sport=SPORT as query parameters.

Valid Sports per Bookmaker

BookmakerSports
Bet365soccer, basketball, tennis, ice-hockey, rugby-league, rugby-union, volleyball, handball, table-tennis, e-sports, american-football, baseball, greyhounds, horse-racing, cricket
Unibet AUamerican-football, australian-rules, baseball, basketball, boxing, cricket, cycling, darts, esports, formula-1, futsal, golf, greyhounds, handball, horse-racing, ice-hockey, lacrosse, mma, motorsports, rugby-league, rugby-union, snooker, soccer, table-tennis, tennis, volleyball
Fanduelamerican-football, baseball, basketball, boxing, cricket, darts, handball, ice-hockey, mma, rugby-league, rugby-union, snooker, soccer, table-tennis, tennis
Bwinamerican-football, baseball, basketball, cricket, darts, futsal, handball, ice-hockey, rugby-league, rugby-union, snooker, soccer, table-tennis, tennis, volleyball
DraftKingsamerican-football, australian-rules, baseball, basketball, boxing, cricket, cycling, darts, esports, golf, handball, ice-hockey, lacrosse, mma, motorsports, rugby-league, rugby-union, snooker, soccer, table-tennis, tennis
Ladbrokesamerican-football, baseball, basketball, boxing, cricket, darts, esports, greyhounds, handball, horse-racing, ice-hockey, mma, rugby-league, rugby-union, soccer, table-tennis, tennis, volleyball
Paddy Poweramerican-football, australian-rules, baseball, basketball, boxing, cricket, darts, esports, golf, greyhounds, handball, horse-racing, ice-hockey, mma, motorsports, rugby-league, rugby-union, snooker, soccer, table-tennis, tennis, volleyball
Betfredbaseball, basketball, boxing, cricket, greyhounds, horse-racing, mma, rugby-league, soccer, tennis
Betano DEamerican-football, baseball, basketball, boxing, cricket, cycling, darts, formula-1, golf, handball, ice-hockey, motorsports, rugby-league, rugby-union, soccer, tennis, volleyball
BetMGM(CO.UK)american-football, baseball, basketball, boxing, cricket, darts, handball, ice-hockey, rugby-league, rugby-union, snooker, soccer, table-tennis, tennis, volleyball
BetMGM(NL)american-football, baseball, basketball, boxing, cricket, darts, handball, ice-hockey, rugby-league, rugby-union, snooker, soccer, table-tennis, tennis, volleyball

Subscribing to a sport with no live events right now is allowed — the server will simply send empty broadcast frames until events appear. Asking for a sport not listed above closes the connection with code 4004.

Query Parameters

ParamRequiredDescription
keyYesYour API key
sportYesSport to stream (soccer, tennis, etc.)

Message Format

On successful connection, the server sends a confirmation message:

json
{
  "type": "connected",
  "bookmaker": "fanduel",
  "sport": "soccer",
  "plan": "pro",
  "validSports": ["soccer", "basketball", "tennis", "ice-hockey", "..."]
}

Then every ~1 second, the server pushes a frame containing all live events for the subscribed sport:

json
{
  "sport": "soccer",
  "timestamp": 1747767600000,
  "count": 12,
  "data": [
    {
      "eventId": "98734521",
      "sport": "soccer",
      "league": "Premier League",
      "home": "Liverpool",
      "away": "Man City",
      "score": "1-0",
      "live": true,
      "startTime": "2026-05-20T19:00:00.000Z",
      "markets": [
        {
          "canonicalMarket": "match_winner",
          "period": "fulltime",
          "selections": [
            { "name": "Liverpool", "decimal": "2.10" },
            { "name": "Draw", "decimal": "3.40" },
            { "name": "Man City", "decimal": "3.25" }
          ]
        }
      ]
    }
  ]
}

Connection Limits

PlanMax Connections
BASIC (Free)0 (REST only)
STARTER (€20/mo)0 (REST only)
PRO1 concurrent
MAX3 concurrent

Code Examples

cURL

bash
# Fetch live soccer events (normalized response)
curl -X GET "https://api.pulsescore.net/api/v3/bet365/live-events?sport=soccer" \
  -H "X-Secret: YOUR_API_KEY"

# Swap the prefix for any other bookmaker — same shape, same paths
curl -X GET "https://api.pulsescore.net/api/fanduel/live-events?sport=soccer" \
  -H "X-Secret: YOUR_API_KEY"

# Fetch soccer leagues (pre-match)
curl -X GET "https://api.pulsescore.net/api/v3/bet365/soccer/leagues" \
  -H "X-Secret: YOUR_API_KEY"

# Fetch events for a league
curl -X GET "https://api.pulsescore.net/api/v3/bet365/soccer/leagues/Premier%20League/events" \
  -H "X-Secret: YOUR_API_KEY"

# Fetch tennis leagues
curl -X GET "https://api.pulsescore.net/api/v3/bet365/tennis/leagues" \
  -H "X-Secret: YOUR_API_KEY"

# Fetch a single event by eventId
curl -X GET "https://api.pulsescore.net/api/v3/bet365/soccer/events/98734521" \
  -H "X-Secret: YOUR_API_KEY"

Python

python
import requests

API_KEY = "YOUR_API_KEY"

# Swap this prefix for any bookmaker — the parser below stays the same
BASE_URL = "https://api.pulsescore.net/api/v3/bet365"
# BASE_URL = "https://api.pulsescore.net/api/fanduel"
# BASE_URL = "https://api.pulsescore.net/api/bwin"

# Fetch live events
response = requests.get(
    f"{BASE_URL}/live-events",
    headers={"X-Secret": API_KEY},
    params={"sport": "soccer"}
)

events = response.json()
for event in events:
    home, away = event["home"], event["away"]
    for market in event["markets"]:
        print(f"{home} vs {away} — {market['canonicalMarket']}")
        for sel in market["selections"]:
            print(f"  {sel['name']}: {sel['decimal']}")

Node.js

javascript
const API_KEY = "YOUR_API_KEY";

// Swap this prefix for any bookmaker — the parser below stays the same
const BASE_URL = "https://api.pulsescore.net/api/v3/bet365";
// const BASE_URL = "https://api.pulsescore.net/api/fanduel";
// const BASE_URL = "https://api.pulsescore.net/api/bwin";

// Fetch live events
const response = await fetch(`${BASE_URL}/live-events?sport=soccer`, {
  headers: { "X-Secret": API_KEY },
});

const events = await response.json();

for (const event of events) {
  console.log(`${event.home} vs ${event.away}`);
  for (const market of event.markets) {
    console.log(`  ${market.canonicalMarket}:`);
    for (const sel of market.selections) {
      console.log(`    ${sel.name}: ${sel.decimal}`);
    }
  }
}

WebSocket (Node.js)

javascript
const WebSocket = require("ws");

const API_KEY = "YOUR_API_KEY";

// All bookmakers with live WebSocket support
const ENDPOINTS = {
  bet365:      "wss://api.pulsescore.net/api/v3/bet365/ws/live",
  unibetau:    "wss://api.pulsescore.net/api/unibetau/ws/live",
  fanduel:     "wss://api.pulsescore.net/api/fanduel/ws/live",
  bwin:        "wss://api.pulsescore.net/api/bwin/ws/live",
  draftkings:  "wss://api.pulsescore.net/api/draftkings/ws/live",
  ladbrokes:   "wss://api.pulsescore.net/api/ladbrokes/ws/live",
  paddypower:  "wss://api.pulsescore.net/api/paddypower/ws/live",
  betfred:     "wss://api.pulsescore.net/api/betfred/ws/live",
  betano-de:   "wss://api.pulsescore.net/api/betano-de/ws/live",
  betmgm-couk: "wss://api.pulsescore.net/api/betmgm-couk/ws/live",
  betmgm-nl:   "wss://api.pulsescore.net/api/betmgm-nl/ws/live",
};

const bookmaker = "fanduel"; // bet365 | unibetau | fanduel | bwin | draftkings | ladbrokes | paddypower | betfred | betano-de | betmgm-couk | betmgm-nl
const sport = "soccer";
const ws = new WebSocket(
  `${ENDPOINTS[bookmaker]}?key=${API_KEY}&sport=${sport}`
);

ws.on("open", () => {
  console.log(`Connected to ${bookmaker} (${sport})`);
});

ws.on("message", (raw) => {
  const msg = JSON.parse(raw);
  if (msg.type === "connected") {
    console.log(`Subscribed to ${msg.bookmaker} ${msg.sport} (plan: ${msg.plan})`);
    console.log(`Valid sports: ${msg.validSports.join(", ")}`);
    return;
  }
  // Broadcast frame
  console.log(`${msg.count} live ${msg.sport} events`);
  for (const event of msg.data) {
    console.log(`  ${event.home} vs ${event.away}`);
  }
});

ws.on("close", (code, reason) => {
  console.log(`Disconnected: ${code} ${reason}`);
});

WebSocket (Python)

python
import asyncio
import json
import websockets

API_KEY = "YOUR_API_KEY"

# All bookmakers with live WebSocket support
ENDPOINTS = {
  "bet365":       "wss://api.pulsescore.net/api/v3/bet365/ws/live",
  "unibetau":     "wss://api.pulsescore.net/api/unibetau/ws/live",
  "fanduel":      "wss://api.pulsescore.net/api/fanduel/ws/live",
  "bwin":         "wss://api.pulsescore.net/api/bwin/ws/live",
  "draftkings":   "wss://api.pulsescore.net/api/draftkings/ws/live",
  "ladbrokes":    "wss://api.pulsescore.net/api/ladbrokes/ws/live",
  "paddypower":   "wss://api.pulsescore.net/api/paddypower/ws/live",
  "betfred":      "wss://api.pulsescore.net/api/betfred/ws/live",
  "betano-de":    "wss://api.pulsescore.net/api/betano-de/ws/live",
  "betmgm-couk":  "wss://api.pulsescore.net/api/betmgm-couk/ws/live",
  "betmgm-nl":    "wss://api.pulsescore.net/api/betmgm-nl/ws/live",
}

BOOKMAKER = "fanduel"  # bet365 | unibetau | fanduel | bwin | draftkings | ladbrokes | paddypower | betfred | betano-de | betmgm-couk | betmgm-nl
SPORT = "soccer"
URL = f"{ENDPOINTS[BOOKMAKER]}?key={API_KEY}&sport={SPORT}"

async def stream():
    async with websockets.connect(URL) as ws:
        print(f"Connected to {BOOKMAKER} ({SPORT})")
        async for message in ws:
            msg = json.loads(message)
            if msg.get("type") == "connected":
                print(f"Subscribed to {msg['bookmaker']} {msg['sport']} (plan: {msg['plan']})")
                print(f"Valid sports: {', '.join(msg['validSports'])}")
                continue
            # Broadcast frame
            for event in msg["data"]:
                print(f"{event['home']} vs {event['away']}")

asyncio.run(stream())

Error Codes

CodeMeaning
200Success
401Invalid or missing API key
403Access denied (plan restriction)
404Resource not found
429Rate limit exceeded
500Internal server error

WebSocket Close Codes

CodeMeaningRetry?
4001Authentication failedNo
4003Plan too low (PRO or MAX required)No
4004Invalid sport for this bookmakerNo
4010Subscription expiredNo
4011Plan downgrade - reconnect requiredYes
4012Session replaced by new connectionYes
4029Connection limit reachedNo

Rate Limits

PlanRequestsRate (per bookmaker)WebSocketPrice
BASIC500/month1 req/secNoneFree
STARTER30,000/month1 req/minNone€20/mo
PROUnlimited1 req/sec1 connection€79/mo
MAXUnlimited3 req/sec3 connections€149/mo

Rate-limited requests return HTTP 429. Implement exponential backoff for retries. WebSocket connections automatically receive updates - no polling needed.