REST API // BASE:classicchess.com // PUBLIC READS // SCOPED ACCOUNT TOKENS

REST API

Master Database / Study Archives / PGN

Public archive data and scoped account collection automation

Everything Andromeda serves to its public archive pages is available over a small, stable REST API. Public archive endpoints need no API key. Account collection automation uses scoped personal API tokens created from profile settings.

Basics

All endpoints live under a single base URL. Every path in this document is relative to it.

https://classicchess.com

Conventions & pagination

List endpoints are paginated. Pass page and page_size as query parameters. The default page size is 50 and the maximum is 100 (larger values are clamped down).

Paginated payloads share a common envelope around their results array:

{
  "count": 128,            // total matches found
  "page": 1,               // current page number
  "page_size": 50,         // items per page
  "page_count": 3,         // total number of pages
  "results": [ ... ],      // the page of items
  "next": "https://classicchess.com/api/v1/games/?q=...&page=2&page_size=50",
  "previous": null         // null on the first page
}

The master game search returns at most 500 matching games per query (reported as result_limit in the response); refine your query q to narrow large result sets. Search strings are capped at 120 characters and 8 searchable terms.

Player resolver

GET /api/v1/players/

Resolve a name fragment against MasterDB player identities and curated public archives.

ParameterRequiredDescription
qyesName fragment, slug, or alias.
limitnoMaximum results per group (default 10, max 50).

Example

curl "https://classicchess.com/api/v1/players/?q=tal"

Errors

Errors use the appropriate HTTP status code and a consistent JSON body with a machine-readable code and a human-readable message:

{
  "error": {
    "code": "missing_query",
    "message": "q is required."
  }
}
StatusCodeMeaning
400missing_queryA required q parameter was empty.
400missing_playerA required player parameter was empty.
400missing_opponenthead_to_head mode needs an opponent.
400invalid_modemode was not summary or head_to_head.
400invalid_paginationpage or page_size was not a positive integer.
404not_foundThe requested game does not exist.
503stats_unavailableThe master database is temporarily not connected.

Master database API

The master database holds millions of historical games (roughly 1840–2000). Use it to look up player statistics, search games, and pull individual game details or raw PGN. Games are addressed by an opaque token returned in every search result.

GET /api/v1/stats/

Player summary statistics, or a head-to-head record between two players.

ParameterRequiredDescription
qyesCanonical stats query. Use one player name for a summary, or two names for head-to-head (e.g. Tal Smyslov).
modelegacysummary (default) or head_to_head when using legacy params.
playerlegacyLegacy player name to resolve (e.g. Capablanca).
opponentlegacy head-to-headLegacy second player; required when mode=head_to_head.

Example

curl "https://classicchess.com/api/v1/stats/?q=Capablanca"

Response

{
  "mode": "summary",
  "query": { "q": "Capablanca", "player": "Capablanca", "opponent": "" },
  "player": {
    "query": "Capablanca",
    "selected": {
      "name": "Capablanca, Jose Raul",
      "search_name": "capablanca jose raul",
      "aliases": ["Capablanca", "Capablanca, J.R."],
      "game_count": 1234,
      "max_elo": 2725
    },
    "candidates": [ { "name": "...", "game_count": 1234, "max_elo": 2725 } ]
  },
  "opponent": null,
  "result": {
    "total": 1234, "wins": 600, "losses": 134, "draws": 500,
    "as_white": 620, "as_black": 614
  },
  "opening_insights": { "...": "per-opening breakdown, or null" }
}

When a name is ambiguous, selected may be null and candidates lists the possible matches — refine the q value to disambiguate. Legacy mode, player, and opponent params remain accepted. For head_to_head, opponent mirrors the player block and result is the record from player's point of view.

GET /api/v1/games/

Search the master database for games. Returns a paginated list of game summaries.

ParameterRequiredDescription
qyesFree-text query. Combine player names, an ECO code, and/or a year (e.g. Fischer Spassky 1972 or Kasparov B33).
pagenoPage number (default 1).
page_sizenoItems per page (default 50, max 100).

Example

curl "https://classicchess.com/api/v1/games/?q=Fischer+Spassky+1972&page_size=10"

Response

{
  "count": 21,
  "page": 1,
  "page_size": 10,
  "page_count": 3,
  "result_limit": 500,
  "query": "Fischer Spassky 1972",
  "results": [
    {
      "token": "a1b2c3d4",
      "white": "Spassky, Boris V",
      "black": "Fischer, Robert James",
      "display_white": "Spassky, Boris V",
      "display_black": "Fischer, Robert James",
      "result": "1/2-1/2",
      "event": "World Championship",
      "site": "Reykjavik",
      "date": "1972.07.11",
      "played_on": "1972-07-11",
      "year": 1972,
      "round": "1",
      "white_elo": 2660,
      "black_elo": 2785,
      "eco": "E56",
      "opening": "Nimzo-Indian Defence",
      "ply_count": 112,
      "move_count": 56,
      "source_count": 3,
      "urls": {
        "api_detail": "/api/v1/games/a1b2c3d4/",
        "api_pgn": "/api/v1/games/a1b2c3d4/pgn/",
        "gif": "/api/v1/games/a1b2c3d4/gif/",
        "html": "/game-search/a1b2c3d4/"
      }
    }
  ],
  "next": "https://classicchess.com/api/v1/games/?q=Fischer+Spassky+1972&page=2&page_size=10",
  "previous": null
}
GET/POST /api/v1/games/export/

Bulk export MasterDB games as a PGN bundle or newline-delimited JSON.

ParameterRequiredDescription
qnoExport the current search result set (up to the API result limit).
tokensnoComma-separated game tokens, max 300. POST may send the same list as plain text, up to 64 KiB.
formatnopgn (default) or ndjson.
pgnInJsonnoInclude PGN in NDJSON rows (default true).

Example

curl "https://classicchess.com/api/v1/games/export/?q=Fischer+Spassky+1972" -o games.pgn
curl "https://classicchess.com/api/v1/games/export/?q=Fischer+Spassky+1972&format=ndjson"
GET /api/v1/games/{token}/

Full detail for one master game, including its full PGN. The token comes from a search result.

Example

curl "https://classicchess.com/api/v1/games/a1b2c3d4/"

The body is the same object shown in search results, plus a "pgn" field containing the full game text.

GET /api/v1/games/{token}/pgn/

The raw PGN for one master game as text/plain, ready to save or import elsewhere.

Example

curl "https://classicchess.com/api/v1/games/a1b2c3d4/pgn/" -o game.pgn
GET /api/v1/games/{token}/gif/

Registered-account animated GIF export for one master game. Add orientation=black to flip the board.

Example

curl -H "Authorization: Bearer $TOKEN" "https://classicchess.com/api/v1/games/a1b2c3d4/gif/" -o game.gif

Study-archive API

The public archive is Andromeda's hand-curated collection of player and event memberships over shared game records. Player-backed list/export results retain a readable player_slug/game_slug token for compatibility; the returned URLs always identify the shared canonical game record.

GET /api/v1/public/players/

List the curated study players (those with at least one game).

ParameterRequiredDescription
qnoFilter players by name, slug, or source alias.

Example

curl "https://classicchess.com/api/v1/public/players/?q=tal"

Response

{
  "source": "public",
  "query": "tal",
  "count": 1,
  "results": [
    {
      "name": "Mikhail Tal",
      "slug": "mikhail-tal",
      "game_count": 2431,
      "criteria": "World Champion 1960-1961",
      "urls": {
        "api_games": "/api/v1/public/games/?archive_player=mikhail-tal",
        "html_games": "/players/mikhail-tal/games/",
        "html_about": "/players/mikhail-tal/about/"
      }
    }
  ]
}
GET /api/v1/public/games/

Search player-backed public archive games. Returns a paginated list of game summaries whose navigable URLs target the shared canonical game record.

ParameterRequiredDescription
qnoFree-text query matched against players, event, site, date, ECO, and opening.
archive_playernoRestrict to one archive by player slug, name, or alias.
sincenoInclude games from this year or later.
untilnoInclude games from this year or earlier.
sortnoasc (default) or desc by game date.
pagenoPage number (default 1).
page_sizenoItems per page (default 50, max 100).

Example

curl "https://classicchess.com/api/v1/public/games/?archive_player=mikhail-tal&since=1960&until=1965&sort=asc"

Response

{
  "source": "public",
  "query": "",
  "archive_player": "mikhail-tal",
  "since": 1960,
  "until": 1965,
  "sort": "asc",
  "count": 42,
  "page": 1,
  "page_size": 50,
  "page_count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "token": "mikhail-tal/tal-vs-larsen-1965",
      "player": { "name": "Mikhail Tal", "slug": "mikhail-tal" },
      "white": "Tal, Mikhail",
      "black": "Larsen, Bent",
      "display_white": "Tal, Mikhail",
      "display_black": "Larsen, Bent",
      "result": "1-0",
      "result_key": "win",
      "event": "Candidates",
      "site": "Bled",
      "date": "1965.07.20",
      "played_on": "1965-07-20",
      "round": "5",
      "white_elo": null,
      "black_elo": null,
      "eco": "B33",
      "opening": "Sicilian, Sveshnikov",
      "move_count": 41,
      "urls": {
        "api_detail": "/api/v1/public/games/tal-vs-larsen-1965/",
        "api_pgn": "/api/v1/public/games/tal-vs-larsen-1965/pgn/",
        "gif": "/games/tal-vs-larsen-1965/gif/",
        "html": "/games/tal-vs-larsen-1965/",
        "pgn_download": "/games/tal-vs-larsen-1965/download/"
      }
    }
  ]
}
GET/POST /api/v1/public/games/export/

Bulk export player-backed public archive games as PGN or newline-delimited JSON. The player-scoped token form remains supported for existing export clients.

ParameterRequiredDescription
qnoFree-text archive search.
archive_playernoRestrict to one archive.
sincenoInclude games from this year or later.
untilnoInclude games from this year or earlier.
tokensnoCompatibility export selector: comma-separated player_slug/game_slug tokens, max 300 and up to 64 KiB. Use each result's canonical URL for game navigation.
formatnopgn (default) or ndjson.
pgnInJsonnoInclude PGN in NDJSON rows (default true).

For POST, send the same compatibility token list as comma- or newline-separated text/plain.

Example

curl "https://classicchess.com/api/v1/public/games/export/?archive_player=mikhail-tal&since=1960&until=1965" -o tal-1960-1965.pgn
GET /api/v1/public/games/{game_slug}/

Canonical full detail for one public archive game. It works for player-backed and event-only games, returns the same shared Game identity, archive memberships, full PGN, and board-ready mainline.

Example

curl "https://classicchess.com/api/v1/public/games/tal-vs-larsen-1965/"
GET /api/v1/public/games/{game_slug}/pgn/

The canonical public PGN as text/plain.

Example

curl "https://classicchess.com/api/v1/public/games/tal-vs-larsen-1965/pgn/" -o tal.pgn
GET /api/v1/public/games/{player_slug}/{game_slug}/

Player-context compatibility detail, retained for existing clients. New integrations should use the canonical game-slug endpoint above.

Example

curl "https://classicchess.com/api/v1/public/games/mikhail-tal/tal-vs-larsen-1965/"
GET /api/v1/public/games/{player_slug}/{game_slug}/pgn/

Player-context compatibility PGN. New integrations should use the canonical game-slug endpoint above.

Example

curl "https://classicchess.com/api/v1/public/games/mikhail-tal/tal-vs-larsen-1965/pgn/" -o tal.pgn

Annotated-book API

Public-domain annotated books are exposed as book metadata, paginated game lists, full annotated game detail, and all-games PGN exports.

GET /api/v1/annotated/books/

List annotated books and their source metadata.

GET /api/v1/annotated/books/{book_slug}/games/

Paginated games for one annotated book.

GET /api/v1/annotated/books/{book_slug}/games/{game_slug}/

One annotated game with PGN and public-domain annotation notes.

GET /api/v1/annotated/books/{book_slug}/pgn/

All games in one annotated book as a PGN bundle.

Opening explorer API

GET /api/v1/opening-explorer/

Explore a position from an indexed Andromeda source. Supports Lichess-style fen + play lookup.

ParameterRequiredDescription
fennoRoot FEN; omitted means the normal starting position.
playnoComma-separated UCI moves from the root, such as d2d4,d7d5; max 100 moves / 512 characters.
movesnoNumber of candidate moves (default 12, max 50).
topGamesnoNumber of reference games (default site setting, max 15).
since / untilnoInclusive year range for sources with year indexes (1400-9999).
source_type / source_keynoSelect a public key returned by the sources endpoint; the master database key is masterdb.

Example

curl "https://classicchess.com/api/v1/opening-explorer/?play=d2d4,d7d5&moves=8&topGames=15&until=1900"
GET /api/v1/opening-explorer/sources/

List currently indexed opening explorer sources.

Mobile app support

GET /api/v1/mobile/home/

Native-app home payload: front-door counts, Game of the Day, and primary API links.

GET /api/v1/mobile/android/release/

Native Android release metadata, including version code, APK URL, size, and SHA-256 checksum for in-app update verification.

Account API

Account endpoints accept bearer credentials. Automation should use personal API tokens created from profile settings; native mobile apps can use the mobile login endpoint to exchange username/password plus TOTP, if enabled, for a mobile session credential. All credentials are stored hashed server-side, revocable, and limited by explicit scopes.

Authorization: Bearer andm_pat_...
Authorization: Bearer andm_mobile_...
ScopeAllows
account:readRead the authenticated account summary.
library:readList account game collections.
library:writeCreate collections and import public archive games into them.
POST /api/v1/account/mobile/login/

Native-app username/password sign-in. If the account has authenticator 2FA enabled, retry with two_factor_code after receiving two_factor_required.

Example

curl -X POST "https://classicchess.com/api/v1/account/mobile/login/" \
  -H "Content-Type: application/json" \
  -d '{"username":"YOUR_USERNAME","password":"YOUR_PASSWORD","device_name":"Android phone"}'
POST /api/v1/account/mobile/logout/

Revoke the current mobile session credential.

GET/POST /api/v1/account/mobile/settings/

Native-app account settings for board theme, piece set, and Stockfish preferences. Requires a mobile session credential.

GET /api/v1/account/me/

Authenticated account summary. Requires account:read.

GET/POST /api/v1/account/collections/

List collections with library:read, or create one with library:write.

Create example

curl -X POST "https://classicchess.com/api/v1/account/collections/" \
  -H "Authorization: Bearer $ANDROMEDA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Tal 1960-1965"}'
GET/PATCH/DELETE /api/v1/account/collections/{id}/

List games inside one owned collection with library:read; rename or delete it with library:write. Other users' collections return 404.

DELETE /api/v1/account/collections/{id}/items/{item_id}/

Remove one game from an owned collection. Requires library:write; other users' collections return 404.

POST /api/v1/account/collections/import/

Create or reuse a collection and import public archive player games. Requires library:write.

Example

curl -X POST "https://classicchess.com/api/v1/account/collections/import/" \
  -H "Authorization: Bearer $ANDROMEDA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"source":"public_player_games","name":"Tal 1960-1965","archive_player":"mikhail-tal","since":1960,"until":1965}'

Response

{
  "ok": true,
  "collection": { "id": 123, "name": "Tal 1960-1965", "sort": "added", "game_count": 42 },
  "created_collection": true,
  "matched": 42,
  "added": 42,
  "already_present": 0,
  "skipped": 0
}
GET/POST /api/v1/account/imported-games/

List/search imported games with library:read, or import one pasted PGN / supported public game URL with library:write.

GET /api/v1/account/imported-games/{slug}/

Owned imported-game detail with PGN and board-ready mainline. Other users' private imports return 404.

Command-line client

If you'd rather not assemble URLs by hand, there is a small, dependency-free Python client. The public download, pull_api.py, is a standalone script that wraps every endpoint above. It needs only Python 3 (no third-party packages) and prints JSON — or PGN with --pgn — to stdout.

In the repository, the reusable client lives in the andromeda_api package. The root pull_api.py script is just a compatibility wrapper around that library, so Python code can import AndromedaClient directly.

↧ Download pull_api.py  or grab it from the shell:

curl -O https://classicchess.com/api/pull_api.py

Then run it

# Master database
    python3 pull_api.py master stats -q Capablanca
python3 pull_api.py master games --players Fischer Spassky --year 1972
python3 pull_api.py master export --players Fischer Spassky --year 1972 --output games.pgn
python3 pull_api.py master pgn a1b2c3d4 --output game.pgn
python3 pull_api.py players tal

# Study archive
python3 pull_api.py public players --plain
python3 pull_api.py public games --archive-player mikhail-tal --eco B33 --all-pages
python3 pull_api.py public games --archive-player mikhail-tal --since 1960 --until 1965
python3 pull_api.py public export --archive-player mikhail-tal --output tal.pgn
python3 pull_api.py public pgn mikhail-tal/tal-vs-larsen-1965

# Account collections
ANDROMEDA_API_TOKEN=... python3 pull_api.py account collections list
ANDROMEDA_API_TOKEN=... python3 pull_api.py account collections import-player "Tal 1960-1965" mikhail-tal --since 1960 --until 1965

Library usage inside the repository

python3 -m andromeda_api players tal

from andromeda_api import AndromedaClient

client = AndromedaClient()
stats = client.master_stats(query="Fischer Spassky")
games = client.master_games("Fischer Spassky 1972", page_size=10)

Add --base-url to point the client at a different host, --all-pages to follow pagination automatically, and --limit to cap the number of results. Account commands read ANDROMEDA_API_TOKEN or --api-token.