A free, open-source REST API for over-the-board chess tournament data. No authentication required. No API key needed. This API currently serves 1800+ tournaments in 90+ countries.
Base URL: https://tourneyradar-api.vercel.app
Interactive docs: browse every endpoint and try requests against live data. Machine-readable spec at /openapi.json.
New here? Getting Started covers local setup, deployment, and a first call to every endpoint.
Returns a paginated list of chess tournaments.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
country |
string | 2-letter country code (e.g. IN, DE, US) |
category |
string | Classical, Rapid, or Blitz |
upcoming |
boolean | true to return only future tournaments |
fide_rated |
boolean | true to return only FIDE rated tournaments |
limit |
number | Results per page (1–100, default 50) |
page |
number | Page number (default 1) |
Example: GET /v1/tournaments?country=IN&upcoming=true&limit=5
Response:
{
"data": [
{
"id": "cr_1234567",
"name": "Chennai Open 2026",
"city": "Chennai",
"country": "India",
"country_code": "IN",
"date": "2026-06-01",
"end_date": "2026-06-07",
"category": "Classical",
"fide_rated": true,
"rounds": 9,
"format": "Swiss",
"lat": 13.0827,
"lng": 80.2707,
"source_url": "https://chess-results.com/..."
}
],
"meta": {
"page": 1,
"limit": 5,
"total": 248,
"hasMore": true
}
}Returns a single tournament by ID.
Example: GET /v1/tournaments/cr_1371843
Response:
{
"data": { ...full tournament object }
}Returns all countries that have tournament data.
Example: GET /v1/countries
Response:
{
"data": [
{ "country_code": "IN", "country": "India" },
{ "country_code": "DE", "country": "Germany" }
]
}Full-text search across tournament name, organizer, and location.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
q |
string | Search term. Required, minimum length 1 |
limit |
number | Results per page (1–1000, default 50) |
page |
number | Page number (minimum 1, default 1) |
PostgREST filter control characters ((, ), ,) are stripped from q before it is matched, so they cannot break the underlying query.
Example: GET /v1/search?q=open&limit=2
Response:
{
"data": [
{
"id": "cr_1350779",
"name": "GPOA OPEN 2026 Přebor JmŠS v rapidu HD16, HD18 a HD20",
"city": "Obchodní Akademie Znojmo",
"country": "Czech Republic",
"country_code": "CZ",
"date": "2026-03-25",
"end_date": "2026-03-25",
"category": "Rapid",
"fide_rated": true,
"rounds": 7,
"format": "Swiss",
"lat": 48.8564399,
"lng": 16.0461196,
"source_url": "https://chess-results.com/tnr1350779.aspx?lan=1"
}
],
"meta": {
"page": 1,
"limit": 2,
"total": 1695,
"hasMore": true
}
}Aggregate figures across all published tournaments: how much data sits behind the API without paging through it.
Example: GET /v1/stats
Response:
{
"data": {
"total": 12989,
"upcoming": 1620,
"countries": 46,
"byCategory": {
"Classical": 230,
"Rapid": 11884,
"Blitz": 875
},
"lastScrapedAt": "2026-08-23T03:31:29.081+00:00"
}
}100 requests per minute per IP, backed by Upstash Redis rather than process memory, so the limit actually holds across serverless invocations. This replaces an earlier in-memory limiter that didn't (#19).
Every response carries X-RateLimit-Limit and X-RateLimit-Remaining. Once
exceeded, requests get 429 with a Retry-After header. If the Upstash store
is unreachable, the API fails open: requests pass through unlimited rather
than the whole API going down.
The limiter only activates once UPSTASH_REDIS_REST_URL and
UPSTASH_REDIS_REST_TOKEN are configured on the deployment (see
.env.example); without them it's a no-op, so self-hosted instances aren't
forced onto Upstash.
Please be reasonable regardless: responses are cached at the edge, so hammering the same query gains you nothing. If you need bulk access, open an issue and say what you are building.
JavaScript / TypeScript
const res = await fetch(
'https://tourneyradar-api.vercel.app/v1/tournaments?country=IN&upcoming=true&limit=5'
)
const { data, meta } = await res.json()
console.log(`Found ${meta.total} tournaments`)
console.log(data.map((tournament) => tournament.name))curl
# List upcoming tournaments in India.
curl "https://tourneyradar-api.vercel.app/v1/tournaments?country=IN&upcoming=true&limit=5"
# Fetch a single tournament by id.
curl "https://tourneyradar-api.vercel.app/v1/tournaments/cr_1371843"Python
import requests
BASE_URL = 'https://tourneyradar-api.vercel.app'
def get_tournaments(country='IN'):
page = 1
tournaments = []
while True:
res = requests.get(
f'{BASE_URL}/v1/tournaments',
params={
'country': country,
'upcoming': 'true',
'limit': 50,
'page': page,
},
timeout=15,
)
res.raise_for_status()
payload = res.json()
tournaments.extend(payload['data'])
if not payload['meta']['hasMore']:
return tournaments
page += 1
for tournament in get_tournaments('IN'):
print(tournament['id'], tournament['name'])Handling missing tournaments
import requests
res = requests.get(
'https://tourneyradar-api.vercel.app/v1/tournaments/not-a-real-id',
timeout=15,
)
if res.status_code == 404:
print(res.json())
# {'error': 'Tournament not found', 'status': 404}
else:
res.raise_for_status()Tournament data is scraped weekly from Chess-Results.com and geocoded via the Google Maps API. Coverage grows with every weekly scrape run.
Using this API in your project? Open a PR adding a line to the table below, or post in Show and tell if you'd rather not touch the README directly.
Format: | [Project name](https://link) | One-line description | @your-github-handle |
| Project | Description | Author |
|---|---|---|
| TourneyRadar | Interactive world map of over-the-board chess tournaments, powered by this API | @AnayDhawan |
- TourneyRadar: the interactive world map powered by this API
Contributions welcome. Please open an issue before submitting a PR for significant changes.
Thanks to everyone who has shipped a route, expanded the docs, or filed a fix.
Apache-2.0. See LICENSE