---
name: snookerapi
description: Snooker results, draws, frame scores, players, world rankings, century and maximum breaks from 1927 to today through the Snooker API (api.snookerapi.net). Use when an agent needs snooker match results, an event's draw or winner, a player's record, rankings or breaks.
---

# Snooker API

Snooker data as JSON: every event, match and player from 1927 to today, frame-by-frame scores where they are known, world rankings, century breaks and maximums. Operated by DeveloperData.net. All times and dates are UTC.

## When to use it

Use it for snooker results, draws, champions, head-to-heads, player records, rankings and breaks, past or current. It does not give betting odds, live ball-by-ball scoring or video.

## Access

- Base URL: `https://api.snookerapi.net/v1` (the `/v1` is optional in the path).
- Send the key in a header, never in the address: `Authorization: Bearer <key>` or `X-API-Key: <key>`. A key in the query string is refused with 400.
- Keys look like `snk_live_` followed by 32 characters. Get one from your account at https://snookerapi.net.
- The free key covers yesterday, today and what is scheduled ahead. The whole record back to 1927 is on the paid plans.

## The calls

Every answer is `{"data": ..., "meta": {...}}`. The `/stats/yesterday` calls cover only matches that finished yesterday (UTC), never today's; each takes `event_id` and `stage`. Lists page with `page` and `per_page` (50 by default, 200 at most), or with `cursor` set to the previous `meta.next_cursor`. Ids are our own and stable: `ev_` events, `mt_` matches, `pl_` players, `cp_` competitions.

| Call | What it answers |
|---|---|
| `GET /events` | Events, newest first. Filters: `season` (2025-26), `year`, `tour` (professional, women, seniors, q_school), `competition_id`, `q` (words in the name) |
| `GET /events/{event_id}` | One event: dates, venue, town, country, champion, runner-up, final score, highest break, prize fund and prize money |
| `GET /matches` | Matches, newest first. Filters: `event_id`, `player_id`, `season`, `stage` (main, qualifying, pre_qualifying), `round` (final, semi_final, quarter_final, last_16 ...), `from`, `to` |
| `GET /matches/{match_id}` | One match, with every frame's points and 50+ breaks where they are known |
| `GET /results/yesterday` | Every match finished yesterday |
| `GET /stats/yesterday` | Yesterday (UTC) in one answer: matches, walkovers, frames, deciding frames, whitewashes, average match length, centuries, highest century, by stage |
| `GET /stats/yesterday/players` | Each player who played yesterday: won, lost, frames for and against, frame win rate, centuries |
| `GET /stats/yesterday/deciders` | Yesterday's matches settled in the deciding frame |
| `GET /stats/yesterday/whitewashes` | Yesterday's matches won without losing a frame |
| `GET /stats/yesterday/upsets` | Yesterday's wins over higher-ranked players, biggest ranking gap first |
| `GET /stats/yesterday/top16` | How the world's top 16 fared yesterday, with every result |
| `GET /stats/yesterday/nations` | Yesterday by nation: players, wins, losses, frames, centuries |
| `GET /stats/yesterday/events` | Yesterday by event and round: matches, frames, deciders, whitewashes, centuries |
| `GET /stats/yesterday/scorelines` | How often each scoreline came up yesterday, by match length |
| `GET /stats/yesterday/centuries` | Yesterday's century breaks, highest first |
| `GET /players` | Players. Filters: `q` (name; the most-played first), `country` |
| `GET /players/{player_id}` | One player: nationality, birth date, matches, wins, titles, seasons, centuries and maximums |
| `GET /rankings` | World rankings for a `season` (newest when left out), its latest list or the `list` named in `meta.lists`; `player_id` gives one player's place in every list |
| `GET /centuries` | Century breaks. Filters: `event_id` (highest first), `player_id`, `season`, `stage`, `min_points` |
| `GET /maximums` | Every officially recognised 147, newest first. Filters: `player_id`, `season`, `year` |
| `GET /export/{part}` | The whole record as a gzip file of JSON lines, one part at a time: events, matches, frames, players, centuries, rankings, maximums or prizes. Paid plans; rebuilt daily |
| `GET /usage` | Your key's plan, calls today, limits and data window. Not counted against the limit |

Find an event, then its final:

```
curl -H "Authorization: Bearer $SNOOKER_KEY" "https://api.snookerapi.net/v1/events?q=2024%20World%20Snooker%20Championship&per_page=1"
{"data":[{"id":"ev_1zds2zzq","name":"2024 World Snooker Championship","season":"2023-24","tour":"professional","kind":"ranking","winner":{"id":"pl_wy3wkk0f","name":"Kyren Wilson"},"final_score":"18-14", ...}],"meta":{"count":1, ...}}

curl -H "Authorization: Bearer $SNOOKER_KEY" "https://api.snookerapi.net/v1/matches?event_id=ev_1zds2zzq&round=final"
{"data":[{"id":"mt_t318e6fh","round":"final","best_of":35,"player_one":{"id":"pl_wy3wkk0f","name":"Kyren Wilson","score":18},"player_two":{"id":"pl_saatwh29","name":"Jak Jones","score":14},"winner_id":"pl_wy3wkk0f","date":"2024-05-05","date_known_to":"day","has_frames":true}],"meta":{...}}
```

One match with its frames:

```
curl -H "X-API-Key: $SNOOKER_KEY" "https://api.snookerapi.net/v1/matches/mt_t318e6fh"
{"data":{"id":"mt_t318e6fh", ..., "frames":[{"number":1,"points_one":129,"points_two":0,"breaks_one":[129],"breaks_two":[]}, ...]},"meta":{"plan":"..."}}
```

Dates: `date` is the day when it is known. Otherwise `played_between` gives the window and `date_known_to` says how close it is: `day`, `round`, `stage`, `event` or `month`.

## MCP server

`https://snookerapi.net/mcp` (JSON-RPC over HTTP POST) gives the same calls as tools, with your key as `Authorization: Bearer <key>`: `find_events`, `get_event`, `find_matches`, `get_match`, `yesterdays_results`, `find_players`, `get_player`, `world_rankings`, `century_breaks`, `maximum_breaks`. A refusal comes back as a tool result with `isError` and the same error JSON as the API.

## Limits

- Free key: 30 calls a minute, 50 a day, yesterday onwards.
- Every answer carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `X-Quota-Remaining`.
- Matches more than 48 hours ahead are not returned on any plan.

## Errors

Errors are `{"error": {"code": "...", "message": "...", "docs": "..."}}`, with `plan` and `pricing` added when a paid plan is needed.

| Status | Code | Means |
|---|---|---|
| 400 | bad_request | A parameter has the wrong shape, or the key was sent in the address |
| 401 | unauthorized | No key, or a key that is not valid |
| 403 | upgrade_required | The record asked for is in the archive the paid plans cover |
| 404 | not_found | No such address or id |
| 422 | unknown_parameter | A parameter the call does not take; the message lists the ones it does |
| 429 | rate_limited / quota_exceeded | Too many calls this minute, or today's calls used; wait for `Retry-After` |
| 500 | server_error | A fault on our side, logged; try again later |

## A typical workflow

1. Call `GET /usage` once to see the plan and the data window.
2. Find the id: `GET /players?q=...`, `GET /events?q=...` or `GET /events?season=...`.
3. Ask for the detail: `GET /matches?player_id=...`, `GET /matches?event_id=...&round=final`, `GET /events/{id}`.
4. For frame scores, open the match with `GET /matches/{id}` when `has_frames` is true.
5. Page with `meta.next_cursor` until it is null; stop early when the answer is found.
6. On 403 `upgrade_required`, tell the person the record is in the archive of the paid plans; on 429, wait for `Retry-After`.
