Getting Started with the PulseScore API
Make your first PulseScore API call: get a free key, fetch live and pre-match odds as normalized JSON, page through boards, use the WebSocket and handle 429s.
To get started with the PulseScore API, create a free account, generate an API key in the dashboard and send it in the X-Secret header with every request to https://api.pulsescore.net. This guide goes from that first call to paging through odds boards, pre-match leagues, single events, the live WebSocket and rate-limit handling.
What do you need before the first call?
- A PulseScore account. The BASIC plan is free: 500 requests a month at 1 req/sec per bookmaker, with every bookmaker included.
- An API key, generated in the dashboard.
- curl, or any HTTP client that sends
Accept-Encoding: gzip.
Paid plans raise the quota and the rate and add the live WebSocket and the Stream API; the pricing page compares them.
Step 1: Create an account and generate a key
Sign up at pulsescore.net, open the Dashboard and click Generate New Key. Copy the key right away: the dashboard shows the full key only once. You can revoke a key and generate a new one at any time.
Keep the key on your server. Anyone who holds it can spend your quota, so never ship it in browser or mobile code.
Step 2: Make your first request
Ask Bet365 for the soccer matches in play right now:
curl --compressed "https://api.pulsescore.net/api/v3/bet365/live-events?sport=soccer" \
-H "X-Secret: YOUR_API_KEY"--compressed sends Accept-Encoding: gzip and inflates the reply, which makes a full board 6–14× smaller on the wire. Send that header from your own client too; browsers, Node's fetch, Python requests and Go's net/http already do.
What does the response look like?
Every list endpoint answers with the same envelope: your position in the list plus the items, here events.
{
"total": 17,
"page": 1,
"limit": 5,
"totalPages": 4,
"hasNextPage": true,
"hasPrevPage": false,
"sport": "soccer",
"events": [
{
"eventId": "196565074",
"sport": "soccer",
"league": "United Kingdom||England Premier League",
"home": "Ipswich",
"away": "Liverpool",
"score": { "home": "0", "away": "1" },
"markets": [
{
"canonicalMarket": "OVER_UNDER",
"rawName": "Match Goals",
"period": "FULL_TIME",
"isActive": true,
"marketId": "196565074:OVER_UNDER:FULL_TIME:match-goals",
"selections": [
{ "canonicalOutcome": "OVER", "rawName": "Over", "odds": 1.5714, "line": 2.5, "isActive": true },
{ "canonicalOutcome": "UNDER", "rawName": "Under", "odds": 2.375, "line": 2.5, "isActive": true }
]
}
]
}
]
}The fields you will use most:
| Field | What it holds |
|---|---|
eventId, league, home, away | Which match it is; ids are per bookmaker |
startTime, live, score | Kick-off and live state; score on in-play events |
canonicalMarket, period | Normalized market and period, e.g. MATCH_RESULT, FULL_TIME |
canonicalOutcome, odds, line | Outcome (HOME, DRAW, OVER), decimal price, handicap or total |
Switch on canonicalMarket, period and canonicalOutcome, and fall back to rawName, the bookmaker's own label, when a normalized value is missing or looks wrong. Some bookmakers also send a slug (the event's path on their site) and, in play, matchClock and periodScores; treat those as optional. The schema section of the docs covers every field.
Step 3: Filter by sport and page through results
/live-events takes an optional sport filter in kebab-case or underscore form (soccer, tennis, ice-hockey, table_tennis). Without it you get every sport. To see which sports are in play right now, with an event count for each, call /live-events/sports.
Lists are paginated with page (default 1) and limit (default 5, maximum 30); keep asking for the next page until hasNextPage is false. Boards change while you page, so an event can slide onto the next page: key what you collect by eventId. In Node.js 18 or later, saved as live.mjs:
const BASE_URL = "https://api.pulsescore.net/api/v3/bet365";
const headers = {
"X-Secret": process.env.PULSESCORE_API_KEY,
"Accept-Encoding": "gzip",
};
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Every event of a paginated list, 30 per request, about one request a second.
async function fetchAll(path, params = {}) {
const events = new Map(); // keyed by eventId: boards move while you page
for (let page = 1; ; page++) {
const query = new URLSearchParams({ ...params, page, limit: 30 });
const res = await fetch(`${BASE_URL}${path}?${query}`, { headers });
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
const body = await res.json();
for (const event of body.events) events.set(event.eventId, event);
if (!body.hasNextPage || body.events.length === 0) break;
await sleep(1050);
}
return [...events.values()];
}
const live = await fetchAll("/live-events", { sport: "soccer" });
console.log(`${live.length} live soccer events`);The one-second pause fits BASIC (1 req/sec per bookmaker) and PRO (1 req/sec); on STARTER (1 req/min) wait a minute between pages.
Step 4: Get pre-match leagues and events
Pre-match boards are organized by sport, with three list endpoints:
GET /{sport}/leaguesreturns leagues A-Z, each with its events embedded (markets included), paginated.GET /{sport}/leagues/:league/eventsreturns every pre-match event of one league with full markets, in one response.GET /{sport}/eventsreturns the upcoming pre-match events of a sport, soonest first, paginated.
Pass the league exactly as /leagues returns it, URL-encoded. On Bet365 that is the Country||League value:
# Soccer leagues (Bet365 also answers at the root: /api/v3/bet365/leagues)
curl --compressed "https://api.pulsescore.net/api/v3/bet365/soccer/leagues?limit=30" \
-H "X-Secret: YOUR_API_KEY"
# Every pre-match event of the Premier League
curl --compressed "https://api.pulsescore.net/api/v3/bet365/soccer/leagues/United%20Kingdom%7C%7CEngland%20Premier%20League/events" \
-H "X-Secret: YOUR_API_KEY"
In code, encodeURIComponent(league) in JavaScript or urllib.parse.quote(league, safe="") in Python does the encoding.
Step 5: Fetch a single event
Two endpoints return one event with every market, wrapped in data:
GET /{sport}/events/:eventIdfor a pre-match event.GET /live-events/events/:eventIdfor an in-play event.
An unknown id is not a 404: you get { "data": null }, and the live endpoint also answers null once the event is no longer in play. Check for null before you read the markets.
curl --compressed "https://api.pulsescore.net/api/v3/bet365/soccer/events/196565074" \
-H "X-Secret: YOUR_API_KEY"Step 6: Switch bookmakers by changing the prefix
PulseScore covers 60+ bookmakers, every plan includes all of them, and each one exposes the same paths and the same normalized schema. Only the prefix changes: Bet365 lives at /api/v3/bet365 and every other bookmaker at /api/{bookmaker}.
https://api.pulsescore.net/api/v3/bet365
https://api.pulsescore.net/api/fanduel
https://api.pulsescore.net/api/ps3838
https://api.pulsescore.net/api/betfair-sb
https://api.pulsescore.net/api/{bookmaker}One difference to remember: on other bookmakers soccer always has its own prefix, as in /api/fanduel/soccer/events, while Bet365 also answers at the root. Each bookmaker has a Swagger page listing its sports and routes at https://api.pulsescore.net/api/{bookmaker}/doc (Bet365: https://api.pulsescore.net/v3/bet365/doc), and the sport availability page shows what every bookmaker covers right now.
Horse racing and greyhounds come as race cards, not league boards: on Bet365 at /sports/horse-racing/races and /greyhounds/races, elsewhere at /horse-racing/races and /greyhounds/races, as in /api/betfair-sb/horse-racing/races. The horse racing odds guide walks through a race card.
Step 7: Stream live odds over WebSocket
On PRO, MAX and ULTRA you can keep a socket open instead of polling /live-events. Pass your key and one sport as query parameters:
wss://api.pulsescore.net/api/v3/bet365/ws/live?key=YOUR_API_KEY&sport=soccerOther bookmakers use wss://api.pulsescore.net/api/{bookmaker}/ws/live. The server first sends a connected message with the bookmaker, the sport, your plan and validSports. Then, about once a second, it pushes a frame with every live event of that sport, with sport, timestamp, count and the events in data, in the same schema as the REST responses. Node.js 22 and later have WebSocket built in:
const url =
"wss://api.pulsescore.net/api/v3/bet365/ws/live" +
`?key=${process.env.PULSESCORE_API_KEY}&sport=soccer`;
const ws = new WebSocket(url);
ws.onmessage = ({ data }) => {
const msg = JSON.parse(data);
if (msg.type === "connected") {
console.log(`Connected to ${msg.bookmaker} ${msg.sport} (${msg.plan})`);
} else if (msg.type === "error") {
console.error(msg.message);
} else {
console.log(`${msg.count} live ${msg.sport} events`);
}
};
ws.onclose = ({ code, reason }) => console.log(`Closed: ${code} ${reason}`);Sockets are counted per bookmaker: PRO allows 1, MAX 3 and ULTRA 6; on BASIC and STARTER the handshake is refused with HTTP 402. A sport the bookmaker doesn't stream closes the socket with 4004 and a bad key with 4001, and 4012 means a newer connection of your own account took the slot, so don't reconnect in a loop on it.
For many bookmakers and sports on one socket, or pre-match boards kept in sync, the Stream API sends each board once and then only what changed, once a second; the Stream API docs cover the protocol.
Step 8: Handle rate limits
Rate limits are counted per bookmaker, so you can poll several bookmakers side by side and pace each one on its own:
| Plan | Requests per bookmaker | Monthly quota |
|---|---|---|
| BASIC | 1 req/sec | 500 |
| STARTER | 1 req/min | 30,000 |
| PRO | 1 req/sec | Unlimited |
| MAX | 3 req/sec | Unlimited |
| ULTRA | 6 req/sec | Unlimited |
Successful responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; the reset value is the window length in seconds, not a timestamp. Over the limit you get HTTP 429, and its message tells you which of three things happened:
- Over your plan's rate: the message says how many requests per second or minute your plan allows per bookmaker. Wait one window and carry on at that pace.
- Key paused: ten 429s within ten seconds on one bookmaker pause the key for 60 seconds. Every request then answers 429 with a Retry-After header, so wait that many seconds.
- Monthly quota exceeded: BASIC and STARTER have a monthly quota, and retrying won't help until the next month or an upgrade.
Back off on the first 429 and the pause never happens. The Python guide builds a client that paces itself and handles all three cases.
Next steps
- Build a paced, reconnecting client in Python.
- Pick between polling, the WebSocket and delta streams with REST vs WebSocket vs delta streams.
Frequently asked questions
Is the PulseScore API free to use?
Yes. The BASIC plan is free and includes every bookmaker, with 500 requests a month at 1 req/sec per bookmaker. Paid plans raise the quota and the rate and add the live WebSocket and the Stream API.
How do I authenticate requests?
Send your API key in the X-Secret header on every REST request; the live WebSocket takes it as the key query parameter instead. Generate and revoke keys in the dashboard, and keep them on your server.
Do all bookmakers return the same JSON format?
Yes. Every bookmaker has the same paths and the same normalized schema, with canonicalMarket, period, canonicalOutcome and decimal odds. Only the URL prefix changes: Bet365 is at /api/v3/bet365 and every other bookmaker at /api/{bookmaker}.
Why am I getting HTTP 429?
Either you went over your plan's per-bookmaker rate, your key is paused for 60 seconds after ten 429s within ten seconds, or a BASIC or STARTER monthly quota is used up. The response message says which, and a paused key also gets a Retry-After header.
How do I get live odds without polling?
On PRO, MAX and ULTRA, open the live WebSocket for a bookmaker and sport: it pushes every live event about once a second. The Stream API goes further, sending each board once and then only the changes, for many bookmakers and sports on one socket.
Keep reading
How to Get Real-Time Bet365 Odds in Python
Fetch live and pre-match Bet365 odds in Python: a paced requests client, pagination, market picking, 429 handling and a reconnecting WebSocket for in-play odds.
Live Odds: REST Polling vs WebSocket vs Delta Streams
REST polling, the live WebSocket or the delta Stream API? How PulseScore's three ways to get live odds compare on limits, bandwidth, code and plans.