Skip to content
Skip the menu

API reference

Ledger API

Read the immutable history of every change to a player's balances, page by page, with the server API.

View as Markdown

On this page

The ledger is the history of a wallet: one entry for every grant, spend, pack delivery and refund, never edited and never deleted. Read it to show a player their history, to answer a support request, or to reconcile your own records. The examples on this page continue from the setup.

List ledger entries

Returns a page of a player's ledger entries, newest first. Use currency to keep one currency, and limit and cursor to walk back in time.

GET/players/{player}/ledger

Authentication: secret key. Idempotency: not needed.

Path parameters

ParameterTypeDescription
playerstringA GameCoin player id, or ext:<your id> URL-encoded once.

Query parameters

ParameterTypeRequiredRules
currencystringnoOnly the entries of this currency (2 to 24 lowercase characters). A well-formed code that has no entries gives an empty list.
limitintegernoPage size, from 1 to 100. Default 20.
cursorstringnoThe nextCursor of the previous page. Opaque: never build one.
LangageLanguage
curl "https://gamecoin.apilow.com/api/v1/players/ext%3Auser-42/ledger?currency=gems&limit=2" \
  -H "Authorization: Bearer $GAMECOIN_SECRET_KEY"

Response 200 OK: a page of LedgerEntry objects and the cursor of the next page.

FieldTypeDescription
entriesarrayThe entries of the page, newest first.
nextCursorstring or nullPass it as cursor to get the next page. null on the last page.
JSON
{
  "entries": [
    {
      "id": "6ac4214e255a2e692cd84196",
      "type": "spend",
      "currency": "gems",
      "paidDelta": 0,
      "bonusDelta": -20,
      "balanceAfter": { "paid": 0, "bonus": 50, "total": 50 },
      "reason": null,
      "metadata": {},
      "ref": { "itemSku": "potion" },
      "createdAt": "2026-10-05T22:14:38.350Z"
    },
    {
      "id": "6ac4214ceab6d4425553aff6",
      "type": "spend",
      "currency": "gems",
      "paidDelta": 0,
      "bonusDelta": -30,
      "balanceAfter": { "paid": 0, "bonus": 70, "total": 70 },
      "reason": "revive",
      "metadata": {},
      "ref": {},
      "createdAt": "2026-10-05T22:14:36.491Z"
    }
  ],
  "nextCursor": "MTc5MTIzODQ3NjQ5MTo2YWM0MjE0Y2VhYjZkNDQyNTU1M2FmZjY"
}

To get the next page, pass the cursor back; the other parameters stay the same:

Bash
curl "https://gamecoin.apilow.com/api/v1/players/ext%3Auser-42/ledger?currency=gems&limit=2&cursor=<nextCursor>" \
  -H "Authorization: Bearer $GAMECOIN_SECRET_KEY"
JSON
{
  "entries": [
    {
      "id": "6ac4214bae33663917ec0ca6",
      "type": "grant",
      "currency": "gems",
      "paidDelta": 0,
      "bonusDelta": 100,
      "balanceAfter": { "paid": 0, "bonus": 100, "total": 100 },
      "reason": "level_up",
      "metadata": { "level": "5" },
      "ref": {},
      "createdAt": "2026-10-05T22:14:35.965Z"
    }
  ],
  "nextCursor": null
}

The SDKs can follow the cursors for you. iterate fetches a page only when your loop reaches it, so you can stop whenever you want; limit is the page size:

LangageLanguage
for await (const entry of gamecoin.ledger.iterate(player, { currency: "gems", limit: 50 })) {
  console.log(entry.id, entry.type, entry.reason);
}

Errors

StatusCodeWhenWhat to do
400VALIDATION_FAILEDlimit is not a whole number from 1 to 100 (details.fieldErrors.limit); currency is malformed; cursor is not one this API issued (CURSOR_INVALID); the player reference is malformed (PLAYER_REF_INVALID)Fix the parameter. Never edit a cursor: pass back the one you received.
404NOT_FOUNDThe player does not existAn ext: player exists after its first write; before that it has no history.
JSON
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Invalid cursor",
    "details": { "fieldErrors": { "cursor": ["CURSOR_INVALID"] } }
  }
}

Notes

  • Every entry carries balanceAfter, the balance right after it, so you can display a running balance without adding anything up. paidDelta and bonusDelta are negative for a spend.
  • ref.itemSku is set on a purchase of an item and ref.orderId on an entry caused by an order (pack delivery, refund). The entry types are listed in Objects.
  • Entries are never edited: an error is corrected by a new entry, not by changing an old one.
  • A parameter this API does not know is ignored, not refused. A currency that is well-formed but unknown to the game is not an error either: you get an empty page.
  • Ids sort by creation time. Paging goes back in time from the newest entry, so entries written while you page do not shift the pages behind you.