The Sessions API gives you the action log of a play session: every action anyone took at the table, in order, with who took it and which pieces it touched. Pull it into a spreadsheet or your own analysis tools.
Only people who can edit the game (its owner and collaborators) can use these endpoints.
What the log contains
The log includes everything, hidden information too: cards in every player's hand, pieces in restricted spaces, the faces of face-down cards. During play those are private to the players who hold them. The log is for the designer, which is why only editors of the game can download it.
How long you can download it
A session's history is kept for 24 hours after the session ends, or 90 days after it ends when the game's owner is on a Pro plan or the session is a Pro session. A session that never ends is deleted 30 days after its table was created, or after it was reopened. A reopened session is listed with its new status, no endedAt, and the expiresAt of a table being played, until it ends again. Someone who can edit the game can also delete a session's history sooner, from the Design Journal. Once the history is deleted the log can no longer be downloaded, so download anything you want to keep before then. Each session's expiresAt says when its history goes.
List sessions
GET /api/v1/games/:gameId/sessions
Lists the game's play sessions whose log can still be downloaded, newest first.
| Query parameter | Description |
|---|---|
limit | How many sessions to return, 1 to 200. Default 50. |
cursor | Where to continue. Pass the previous response's nextCursor unchanged to get the next page. |
curl https://playtestparlor.com/api/v1/games/GAME_ID/sessions \
-H "Authorization: Bearer pp_your_token"
{
"sessions": [
{
"id": "k17d...",
"name": "Friday Night",
"status": "ended",
"createdAt": 1790000000000,
"endedAt": 1790007200000,
"expiresAt": 1790093600000,
"eventCount": 412
}
],
"nextCursor": null
}
nextCursor is null when there are no more sessions. eventCount is the number of actions that stand in the session's history: actions that were undone are not counted.
Download the action log
GET /api/v1/games/:gameId/sessions/:sessionId/action-log?format=csv
GET /api/v1/games/:gameId/sessions/:sessionId/action-log?format=json
format is csv (the default) or json. The response is a file download.
The file is sent as it is read, a part at a time, so a long session starts downloading at once. A problem found before the file starts (no access, no such session, history expired) comes back as an error status, listed under Errors. A problem found after the file has started, such as your edit rights being removed or the table's history changing partway, cannot change the status any more, so the transfer ends early instead: curl exits with an error and a browser marks the download as failed. Throw away the partial file and download again.
curl -o log.csv "https://playtestparlor.com/api/v1/games/GAME_ID/sessions/SESSION_ID/action-log?format=csv" \
-H "Authorization: Bearer pp_your_token"
CSV columns
One row per action, in order.
| Column | Meaning |
|---|---|
seq | The action's number in the session, starting at 1. |
timestamp | When it happened, as an ISO 8601 time in UTC. |
elapsed_ms | Milliseconds since the table was created. |
seat_index | Who acted. 1 to 16: a player's seat. 0: the host acting without a seat, or a game editor adding pieces from the game. A trigger that runs on its own (when a timer runs out, or when the session starts) is also recorded as 0, so the log cannot tell it apart from the host; its action_type is sequence.execute or trigger.sessionStart. A trigger a player sets off, such as by pressing a button, is recorded under that player's seat. -1: nobody, the table acting by itself, such as a timer running out, and some changes made from outside the table: importing, or wiring a button or timer to a sequence. |
seat_name | That seat's player name: whoever sits there now, or for a seat nobody sits in now, the name the log last recorded for it up to that action. When the log never recorded a name it reads Seat 3, Host (seat 0) or System (seat -1). |
seat_color | That seat's color, as a hex code. |
action_type | The action's internal name, such as stack.draw_multiple. Stable, so it is the column to filter on. |
description | The action in plain words, such as "Seat 2 drew 3 cards from Deck A". The same wording the replay timeline shows. It counts the pieces the action acted on, not the cards already in a hand that slid along when a card joined or left it. An action recorded before the table's history was converted to its current form (October 2026) keeps the wording it was recorded with, which counted those cards too. |
piece_names | Every piece the action touched, separated by ; , including cards that only slid along in a hand. |
piece_ids | The ids of those pieces, in the same order. |
A cell that starts with =, +, - or @ is written with a leading ' so a spreadsheet does not run a player-chosen name as a formula.
JSON shape
{
"session": {
"id": "k17d...",
"name": "Friday Night",
"gameId": "j57a...",
"createdAt": 1790000000000,
"endedAt": 1790007200000,
"expiresAt": 1790093600000
},
"exportedAt": 1790010000000,
"events": [
{
"seq": 1,
"timestamp": 1790000012000,
"elapsedMs": 12000,
"seatIndex": 1,
"seatName": "Ada",
"seatColor": "#e11d48",
"actionType": "stack.draw_multiple",
"description": "Seat 1 drew 3 cards from Deck A",
"pieces": [
{ "id": "p1...", "name": "Deck A", "type": "stack" },
{ "id": "p2...", "name": "Goblin", "type": "tile" }
]
}
],
"seats": [
{ "seatIndex": 1, "displayName": "Ada", "color": "#e11d48" }
]
}
seats lists every seat the log names, and comes last because the whole log has to be read to know them. The file puts one action per line inside events.
Piece type uses the table's internal names: tile is a card and stack is a deck.
What an action includes
The log records the actions that change the table and that undo can take back. Undone actions are not in it, whether or not anyone acted after the undo. The file is the table's history as it stood when the download started: actions taken, undone or redone after that do not change it. Undoing the newest action that stood when the download started, or resetting the table, changes that history, and the download stops (see HISTORY_CHANGED below). Pointer movement, chat and presence are not table actions and are not logged.
Errors
| Code | HTTP Status | When |
|---|---|---|
GAME_NOT_FOUND | 404 | The game does not exist. |
SESSION_NOT_FOUND | 404 | The session does not exist, is not a play session, belongs to another game, or its game has been deleted. |
PERMISSION_DENIED | 403 | You cannot edit this game. |
HISTORY_EXPIRED | 410 | The session's history has expired and is gone. |
HISTORY_CHANGED | 409 | The session's history changed while the log downloaded: the newest action that stood when the download started was undone, or the table was reset. Download again. Because the file is sent as it is read, you usually see this as a download that ends early rather than as a status. |
VALIDATION_ERROR | 400 | format or limit is not one of the allowed values, or cursor is not a nextCursor this API gave you. |