Skip to content
Pickleball Live API

Pickleball scores API documentation

Start with REST to request matches, scores, timelines and historical data. When you need updates without repeatedly asking for them, connect to the WebSocket feed. Every response uses JSON, and the examples below are designed to be copied and adapted.

Your first five minutes

  1. 1. Create and verify your account

    Registration starts a 14-day Pro trial. Once your email is verified, your API key becomes active.

  2. 2. Copy the key from your dashboard

    Keep the key on your server or in a secure environment variable. Do not place it in public browser code or commit it to a repository.

  3. 3. Request the live match list

    Copy the curl example below and replace pbl_live_YOUR_KEY with your own key. A successful response returns JSON containing the matches currently in progress.

  4. 4. Open one match

    Use the returned match id with the match and state endpoints to retrieve details and the latest scoreboard.

  5. 5. Add live updates

    During the trial or on Pro, have your server create a short-lived ticket. It can hand that single-use ticket to a browser, which connects to the WebSocket feed and subscribes to a match topic for a full score followed by live changes.

Overview

The base URL for all requests is your regional API host. Examples on this page use a placeholder:

Base URL   https://pickleball-api.com
Version    /v1  (in the path — breaking changes ship under a new version)
Format     application/json; UTF-8
Streaming  wss://pickleball-api.com/v1/live

Each account receives one API key. You can still invite teammates to the dashboard, and there is no charge per person. Your plan limits how many requests and live messages the account can use. Every new verified account begins with a 14-day Pro trial, with no card required.

Authentication

Authenticate with your API key as a Bearer token. Keys are prefixed pbl_live_. Send it in the Authorization header on every request — never in a query string or client-side code you ship to end users.

curl -s "https://pickleball-api.com/v1/matches?live=true" \
  -H "Authorization: Bearer pbl_live_YOUR_KEY"

Missing or invalid credentials return 401 unauthorized. A valid key on a plan that lacks the requested feature (for example, WebSockets on Basic) returns 403.

Browser-direct REST requests are not supported: API responses do not include CORS permission headers. Call REST endpoints and mint realtime tickets from your server. If your user interface needs a direct live connection, return only the short-lived ticket to it; never return the API key.

Server clients do not need a browser-style User-Agent, although a descriptive value is useful for support and traffic diagnostics. An authenticated API request must not be challenged merely because it comes from a normal HTTP client.

Response format

Successful responses wrap the payload in a consistent envelope. data is the resource (object or array); meta carries the request id, a server timestamp, and pagination when applicable.

{
  "data": [ /* resource or list */ ],
  "meta": {
    "request_id": "b1f6…",
    "generated_at": "2026-07-14T11:16:18Z",
    "pagination": { "next_cursor": null, "has_more": false }
  }
}

Always log meta.request_id — quote it in support requests and it lets us trace a single call end to end.

Errors

Errors are designed to be useful

Every error response includes a stable code and a request_id. Your application can react to the code, while support can use the request ID to trace the individual call.

A 401 usually means the key is missing or invalid. A 403 means the account does not include the requested feature. A 429 means a rate or monthly limit has been reached. For live streams, resync_required means your app should fetch the latest state through REST before continuing.

Errors use application/problem+json (RFC 9457) with a stable machine readable code, an HTTP status, and the request_id. Branch on code, not on human-readable text.

{
  "type": "urn:pickleball-api:error:unauthorized",
  "title": "Missing or invalid API key",
  "status": 401,
  "code": "unauthorized",
  "request_id": "8f9dcae7-…"
}
  • 400 invalid_request — malformed parameters or body.
  • 401 unauthorized — missing or invalid API key.
  • 403 websocket_not_entitled — feature not on your plan.
  • 404 not_found — no such resource.
  • 429 quota_exceeded — rate or monthly volume cap reached.

Pagination

List endpoints use opaque cursor pagination. Pass limit (default 100, max 500) and follow meta.pagination.next_cursor until has_more is false. Do not construct cursors yourself.

curl -s "https://pickleball-api.com/v1/matches?limit=100&cursor=CURSOR" \
  -H "Authorization: Bearer pbl_live_YOUR_KEY"

Caching & conditional requests

List responses set Cache-Control: private, max-age=5 so you can safely coalesce bursts of reads. The single match state endpoint returns an ETag keyed on resource_version; send it back as If-None-Match to get a cheap 304 Not Modified when nothing has changed.

curl -s "https://pickleball-api.com/v1/matches/MATCH_ID/state" \
  -H "Authorization: Bearer pbl_live_YOUR_KEY" \
  -H 'If-None-Match: "rv-24"'   # -> 304 if unchanged

Do not connect each website visitor directly to the upstream API. Make requests from your server, cache the latest state and share that cached state with your own users.

Rate limits & usage

Each plan has both a short-term request-rate limit and a monthly allowance. Basic includes 25,000 REST requests per month at up to 60 requests per minute. Pro includes 100,000 REST requests per month at up to 120 requests per minute, plus a separate allowance for outbound WebSocket messages.

For a typical MLP-style event of about 30 ties, polling the active tie every 10 seconds uses roughly 15,700 state requests before list, metadata and retry traffic. Polling every 15 seconds uses roughly 10,400. This is why Basic is intended for 10–15 second polling, while Pro customers should use WebSockets for genuinely live updates.

Limits are enforced server-side and shown in response headers. When a rate or monthly cap is reached, the API returns 429 quota_exceeded. Self-serve plans have hard caps, so there are no automatic overage charges. Check current consumption at any time with GET /v1/usage.

GET /v1/usage

{
  "data": {
    "plan": "pro",
    "period_start": "2026-07-01T00:00:00Z",
    "period_end": "2026-08-01T00:00:00Z",
    "rest_calls_used": 12840,
    "rest_calls_limit": 100000,
    "outbound_messages_used": 0,
    "outbound_messages_limit": 100000,
    "sockets_active": 0,
    "sockets_limit": 3
  }
}

Recommended request pattern

For a REST-only application:

  1. Request GET /v1/matches?live=true once every 60 seconds while an event is active.
  2. Fetch match metadata once when a tie first appears.
  3. Poll GET /v1/matches/{id}/state every 10–15 seconds only for ties the user is actively following.
  4. Send If-None-Match with the last ETag so unchanged scores return 304.
  5. Fetch /timeline or /games when the relevant screen is opened or after the tie finishes.

For a Pro application:

  1. Fetch the live match list and initial state through REST.
  2. Open one server-side WebSocket connection.
  3. Subscribe to matches.live or to the individual match topics you need.
  4. Apply incoming deltas in resource_version order.
  5. If the service sends resync_required, fetch the current state through REST and resume.
  6. Fan the resulting state out to your own users from your server.
  7. Reconcile the live set with GET /v1/matches?live=true about once a minute. matches.live has no separate join/leave membership event.

This pattern keeps usage low, protects the API key and gives end users faster responses than making an upstream request for every page view.

REST endpoints

All endpoints require authentication and are prefixed with the base URL.

Matches

GET/v1/matchesList matches. Filters: live=true, status, cursor, limit.
GET/v1/matches/{id}Match metadata.
GET/v1/matches/{id}/stateCurrent scoreboard state (ETag / 304).
GET/v1/matches/{id}/timelineOrdered score events for the match.
GET/v1/matches/{id}/gamesCurrent game number and games-won totals. Per-game point scores are not retained for every feed.

Reference data

GET/v1/tournamentsList tournaments.
GET/v1/tournaments/{id}Tournament detail.
GET/v1/competitionsList competitions / tours.
GET/v1/playersList players.
GET/v1/players/{id}Player detail.
GET/v1/rulesets/{id}Ruleset version definition (scoring rules).

Account & realtime

GET/v1/usageCurrent-period usage vs. limits.
POST/v1/realtime/ticketsMint a short-lived WebSocket ticket (Pro / trial).

The games endpoint currently returns the aggregate scoreboard fields below. It does not promise a list of per-game point scores; use the timeline when the source feed supplies score events. Some snapshot-only feeds do not provide enough history to reconstruct earlier game scores.

{
  "data": {
    "current_game": 1,
    "games_won": [0, 1]
  }
}

The match state object

This is the main score object for a match. data_status describes the freshness or review status of the data, not whether the match is in progress. Its values are live, delayed, stale, under_review and official. A completed match may still have data_status: "live" when its final update came through the live feed. Use lifecycle === "in_progress" for a live board and lifecycle === "completed" for a final result. resource_version rises each time the score changes, helping your app keep updates in the right order. side_0_label and side_1_label name the two players or teams without pretending that pickleball always has a home and away side.

{
  "match_id": "0190…0060",
  "lifecycle": "in_progress",
  "result_method": null,
  "data_status": "live",
  "last_updated_at": "2026-07-14T11:16:18Z",
  "resource_version": 24,
  "source_sequence": 15,
  "ruleset_version_id": "0190…0002",
  "discipline": "doubles",
  "current_game": 1,
  "sides": [
    { "side": 0, "points": 2, "games_won": 0 },
    { "side": 1, "points": 3, "games_won": 0 }
  ],
  "serving": {
    "serving_side": 1,
    "serving_player_id": null,
    "receiving_player_id": null,
    "server_number": 2,
    "service_court": "right"
  },
  "winner_side": null
}

WebSocket streaming

The live feed is included with Pro and the free trial. Pro allows up to 3 concurrent connections, 25 subscribed topics per connection and 100,000 outbound messages each month. Connect to wss://pickleball-api.com/v1/live and choose the matches you want to follow. The service first sends the full current score, then sends smaller update messages whenever something changes.

1. Authenticate

A server-side WebSocket can send your API key as a Bearer token on the upgrade request. For a browser, where you can't set that header, mint a single-use ticket on your server and pass only the ticket to the browser as a query parameter. Tickets expire in 60 seconds and are consumed on use.

Every successful ticket connection is an upstream socket and counts against the account's concurrent socket limit. For a website, prefer one server-side connection and fan out from your own service. Direct browser tickets are best for constrained clients, small demos, or deliberately limited audiences.

# Mint a ticket
curl -s -X POST "https://pickleball-api.com/v1/realtime/tickets" \
  -H "Authorization: Bearer pbl_live_YOUR_KEY"
# -> { "data": { "ticket": "…", "expires_in_seconds": 60,
#                "stream_url": "wss://pickleball-api.com/v1/live" } }

# Connect
wss://pickleball-api.com/v1/live?ticket=TICKET

2. Subscribe

On connect the server sends connection.ready. Send a subscribe message with one or more topics; the server replies subscription.ack and then current state. An individual match topic gets one snapshot. matches.live gets one snapshot on each matching match topic for every match currently in progress; it gets no snapshot when the live set is empty. Later score and lifecycle changes are sent on those match topics.

// client -> server
{ "type": "subscribe", "topics": ["match.0190…0060.state"] }

// topics
match.<match_id>.state   // one match's scoreboard
matches.live             // all in-progress matches

3. Receive updates

Subsequent messages are typed on the type field:

WebSocket state messages do not include side_0_label or side_1_label. Fetch match metadata through REST when a match id is first seen, then cache those labels alongside the streamed state.

  • snapshot — full current state, sent right after you subscribe when a matching state exists.
  • match.score — a scoreboard delta with resource_version.
  • resync_required — refetch state via REST, then resume.
  • ping / rate_limit / error — control messages.

4. Heartbeats & close codes

The server sends ping roughly every 25s; reply with { "type": "pong", "at": "…" }. Miss the pong window (~60s) and the connection closes. Application close codes:

4401  unauthorized        (bad/absent credentials; ticket_invalid_or_expired close reason for a bad, expired, or consumed ticket)
4403  forbidden           (plan lacks WebSocket access)
4408  quota_exceeded      (socket or topic limit hit)
1012  service_restart     (deploy — reconnect with backoff)
1013  try_again_later     (transient; reconnect with backoff)

Rulesets & scoring

Pickleball events can use different scoring rules. Rather than making your app guess from a score such as “8–6”, each match links to a clear ruleset. Fetch that ruleset from /v1/rulesets/{id} when you need to understand how points, games and serving work. Important fields include:

  • point_award: side_out (only the serving side scores) or rally (every rally scores).
  • two_server_doubles: whether doubles uses the two-server rotation, which drives server_number.
  • points_to_win_game, win_by, game_cap, games_to_win, and deciding-game overrides.

Launch coverage includes PPA Tour, Major League Pickleball, and APP Tour rule variants.

Machine-readable specs

The downloadable specification describes the same fields used by the live service. It includes OpenAPI for REST, AsyncAPI for WebSockets and JSON Schema for the main data objects. You can use these files to explore the API in development tools or generate a client in your preferred language.

Ask support for the latest bundle, or generate typed clients directly from the OpenAPI and AsyncAPI documents.

Support

Check the status page for uptime and incidents, or contact support. Include your request_id and we'll trace it.