Every chess.com game has a game ID. For example https://www.chess.com/game/live/2485075845 2485075845 is the game ID. When you go to this URL it is possible to download the PGN by clicking the share button.
Is there a way to do this programmatically (say using Python) so I can pass in the game ID and download it directly using a Python script
I noticed that https://www.chess.com/callback/live/game/2485075845 gives all the data that is related to the game but the moveList is showing in some kind of hashed format which I have no idea how to decode.

Appreciate if anyone can help with this.

  • I'd suggest that chess.com probably are protecting or obfuscating the format so that it will be very expensive to use user-centered methods to download PGN games. They may also have additional layers of protection. See the article on Web Scraping on Wikipedia, especially the material on Legal Issues. You are probably better off asking chess.com for a B2B interface: but if they provide it, it is likely to be at a price.
    – user30536
    Commented Nov 22, 2023 at 18:30

2 Answers 2


It is possible to download games from chess.com, otherwise services like Opening Tree would not work.

Chess.com has published it's data API.

It includes endpoints for downloading games in PGN format.

You can access this API programmatically, including Python. It is possible that you have to download multiple games from a player in a batch and then filter the game you're interested in (e.g, using the game URL/game ID, see data format below).

The data format for games that are returned by this API is

  "white": { // details of the white-piece player:
    "username": "string", // the username
    "rating": 1492, // the player's rating after the game finished
    "result": "string", // see "Game results codes" section
    "@id": "string" // URL of this player's profile
  "black": { // details of the black-piece player:
    "username": "string", // the username
    "rating": 1942, // the player's rating after the game finished
    "result": "string", // see "Game results codes" section
    "@id": "string" // URL of this player's profile
  "accuracies": { // player's accuracies, if they were previously calculated
    "white": float,
    "black": float
  "url": "string", // URL of this game
  "fen": "string", // final FEN
  "pgn": "string", // final PGN
  "start_time": 1254438881, // timestamp of the game start (Daily Chess only)
  "end_time": 1254670734, // timestamp of the game end
  "time_control": "string", // PGN-compliant time control
  "rules": "string", // game variant information (e.g., "chess960")
  "eco": "string", //URL pointing to ECO opening (if available),
  "tournament": "string", //URL pointing to tournament (if available),  
  "match": "string", //URL pointing to team match (if available)  
  • does most of these apis require a userId as well. Since i'm only interested in a single game it would take a lot of time to say filter it out from a user having 1000s of games
    – cmgchess
    Commented Nov 23, 2023 at 3:59
  • 1
    Why do you think would it take a lot of time to filter the games? Even processing 1000 games should not take more than a fraction of a second. The server response will not be slower for 1000 games than for 1 game, except the actual download time (that's of course longer for more data). Plus, you can get monthly archives, so it will never be 1000s of games, unless the player really played 1000s of games in a single month.
    – Hauptideal
    Commented Nov 23, 2023 at 13:55
  • actually my plan is to collect all the game ids of a certain titled tuesday and create a pgn of all the games. will see how it goes
    – cmgchess
    Commented Nov 23, 2023 at 15:56
  • 2
    Thanks it worked and i managed to achieve what i wanted. Link for anyone interested
    – cmgchess
    Commented Dec 3, 2023 at 10:12

There is a better solution for this

The moveList field of https://www.chess.com/callback/live/game/2485075845 gv2UmC92lBZRbs!Tpx8!cu5QfH6ZegWOHA0KBKRKuI98vMZSMS1SnvQBsmTZIBKBmBZPAS!?SJPJCJ7Jks47BrJAdc8mfnOGcf78rGAIGrIPad2VrBVufmunmn
Notice that this string is 2 times the length of the total moves (PlyCount). So each move is encoded into 2 bytes.

Doing some further research found out that this format that chess.com is something called TCN as seen here. This can be decoded to UCI format using the scripts in https://github.com/savi2w/chess-tcn/blob/main/tcn.js

This format will give for each move the data fields from,to,drop,promotion which is standard for UCI.

Now if you have a list of decoded moves, you can convert it to pgn easily using popular chess libraries. I managed to do this in both chess.js and python-chess

Here is how I did it with chess.js

I also improved on the script used to download tournament games using this way. Now implemented with python-chess

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.