# API objects

> The shape of every object the GameCoin API returns, with its fields, its possible values and a real example.

This page describes the objects that the operations of the [API reference](/docs/api) return. Every example below is a real response from the test environment.

**Conventions.** Field names are in camelCase. A type written `string or null` is always present and is `null` when there is no value: a field is never missing. Dates are ISO 8601 strings in UTC, with milliseconds. Amounts are integers: virtual currencies in their smallest unit, euro prices in cents. Ids are 24 hexadecimal characters. Unknown fields may be added to a response in the future: ignore the ones you do not know.

## Player

A person who plays your game.

| Field | Type | Description |
|---|---|---|
| `id` | string | The GameCoin player id. |
| `externalId` | string or `null` | Your own id for the player, without the `ext:` prefix. `null` for an anonymous player. |
| `kind` | string | `external` for a player created by your backend, `hosted` for an anonymous player created by the client API. |
| `displayName` | string or `null` | The name shown in your game. |
| `country` | string or `null` | The ISO 3166-1 alpha-2 country, such as `BE`. |
| `blocked` | boolean | `true` when the player is blocked: they are refused on every client route and on wallet and inventory writes. |
| `createdAt` | string | When the player was created. |

The e-mail address and the birth year you may store with [Create or update a player](/docs/api/players#create-or-update-a-player) are never returned.

```json
{
  "id": "665f1c2e8a3b4d5e6f708192",
  "externalId": "user-42",
  "kind": "external",
  "displayName": "Alice",
  "country": "BE",
  "blocked": false,
  "createdAt": "2026-10-05T22:14:34.808Z"
}
```

## Balance

The balance of a player in one currency, split in two parts.

| Field | Type | Description |
|---|---|---|
| `currency` | string | The currency code. |
| `paid` | integer | Units bought with real money. This is the part a refund takes back. |
| `bonus` | integer | Units granted or earned for free. |
| `total` | integer | `paid` plus `bonus`. |

`paid` and `total` can be negative after a refund of coins that were already spent: see [Refund an order](/docs/api/orders#refund-an-order).

```json
{ "currency": "gems", "paid": 0, "bonus": 100, "total": 100 }
```

## Wallet

All the balances of a player.

| Field | Type | Description |
|---|---|---|
| `playerId` | string | The player's id. |
| `balances` | array of [Balance](#balance) | One balance per active currency, zeros included. |

```json
{
  "playerId": "665f1c2e8a3b4d5e6f708192",
  "balances": [
    { "currency": "gems", "paid": 0, "bonus": 70, "total": 70 },
    { "currency": "gold", "paid": 0, "bonus": 0, "total": 0 }
  ]
}
```

## LedgerEntry

One immutable line of a player's ledger: what changed a balance, and the balance right after.

| Field | Type | Description |
|---|---|---|
| `id` | string | The entry id. Ids sort by creation time. |
| `type` | string | What produced the entry: see [Ledger entry types](#ledger-entry-types). |
| `currency` | string | The currency code. |
| `paidDelta` | integer | The change of the `paid` part: negative when coins are taken. |
| `bonusDelta` | integer | The change of the `bonus` part: negative when coins are taken. |
| `balanceAfter` | object | The balance right after the entry: `paid`, `bonus` and `total`. |
| `reason` | string or `null` | The `reason` you gave, if any. |
| `metadata` | object | The `metadata` you gave: `string` to `string` pairs, `{}` when none. A paid order sets `pack`, the sku of the pack. |
| `ref` | object | What the entry relates to: `orderId` for an entry caused by an order, `itemSku` for the purchase of an item. `{}` when it relates to nothing. |
| `createdAt` | string | When the entry was written. |

A grant, with its `reason` and `metadata`:

```json
{
  "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"
}
```

The delivery of a pack, written when the order was paid:

```json
{
  "id": "6ac4214aad31ce32f56b16e4",
  "type": "purchase_credit",
  "currency": "gems",
  "paidDelta": 500,
  "bonusDelta": 50,
  "balanceAfter": { "paid": 500, "bonus": 50, "total": 550 },
  "reason": null,
  "metadata": { "pack": "gems-500" },
  "ref": { "orderId": "665f1c2e8a3b4d5e6f7081b4" },
  "createdAt": "2026-10-05T22:14:34.070Z"
}
```

### Ledger entry types

| `type` | Written when | `paidDelta` / `bonusDelta` |
|---|---|---|
| `grant` | Your backend [grants currency](/docs/api/wallet#grant-currency) | `bonusDelta` positive (a debt is repaid first) |
| `spend` | Currency is [spent](/docs/api/wallet#spend-currency), or an item is [bought](/docs/api/purchases#buy-an-item-with-currency) (`ref.itemSku`) | Negative |
| `purchase_credit` | An order is paid and its pack delivered (`ref.orderId`) | `paidDelta` is the pack's `amount`, `bonusDelta` its `bonus` |
| `refund_clawback` | An order is [refunded](/docs/api/orders#refund-an-order) (`ref.orderId`) | Negative |
| `chargeback_clawback` | The cardholder disputes the payment of an order (`ref.orderId`) | Negative |
| `adjustment` | A manual correction made by the studio | Either sign |
| `gift_code` | A player redeems a [gift code](/docs/api/codes#redeem-a-gift-code) (`metadata.code` is the code) | `bonusDelta` positive: gift codes only give free coins |

## InventoryItem

A quantity of an item that a player owns.

| Field | Type | Description |
|---|---|---|
| `sku` | string | The sku of the item. |
| `quantity` | integer | Units owned. `0` is possible right after a consumption, never in an inventory list. |
| `updatedAt` | string | When the quantity last changed. |

```json
{ "sku": "potion", "quantity": 3, "updatedAt": "2026-10-05T22:14:37.251Z" }
```

## Currency

A currency of the game.

| Field | Type | Description |
|---|---|---|
| `code` | string | The currency code: lowercase letters, digits and `_`, such as `gems`. |
| `name` | string | The display name. |
| `purchasable` | boolean | `true` when packs can sell this currency for real money. Its balances then have a `paid` part. |

```json
{ "code": "gems", "name": "Gems", "purchasable": true }
```

## Item

An item of the game: something a player can own.

| Field | Type | Description |
|---|---|---|
| `sku` | string | The sku of the item. |
| `name` | string | The display name. |
| `description` | string or `null` | The display description. |
| `type` | string | `consumable` (can be consumed) or `durable` (cannot). |
| `price` | object or `null` | The price in a currency: `currency` and `amount`. `null` when the item cannot be bought with currency. |
| `maxOwned` | integer or `null` | The most units a player can own. `null` for no limit. |
| `clientPurchasable` | boolean | `true` when the client API may sell the item. |
| `metadata` | object | Free `string` to `string` pairs set on the item in the dashboard. |

```json
{
  "sku": "potion",
  "name": "Potion",
  "description": null,
  "type": "consumable",
  "price": { "currency": "gems", "amount": 20 },
  "maxOwned": null,
  "clientPurchasable": true,
  "metadata": {}
}
```

## Pack

A bundle of currency and items sold for real money.

| Field | Type | Description |
|---|---|---|
| `sku` | string | The sku of the pack. |
| `name` | string | The display name. |
| `description` | string or `null` | The display description. |
| `priceCents` | integer | The price in euro cents, VAT included. |
| `currency` | string | Always `EUR`. |
| `grants` | array | The currency delivered: `currency`, `amount` (credited to `paid`) and `bonus` (credited to `bonus`). |
| `items` | array | The items delivered: `sku` and `quantity`. |
| `badge` | string or `null` | A short label such as "Best value". |
| `maxPerPlayer` | integer or `null` | The most paid orders a player can have for this pack. `null` for no limit. |
| `availability` | [PackAvailability](#packavailability) or `null` | The sale window, the stock left and the founder flag. `null` for an ordinary pack, always on sale without limit. |

```json
{
  "sku": "starter",
  "name": "Starter pack",
  "description": null,
  "priceCents": 99,
  "currency": "EUR",
  "grants": [{ "currency": "gems", "amount": 100, "bonus": 20 }],
  "items": [{ "sku": "potion", "quantity": 1 }],
  "badge": null,
  "maxPerPlayer": 1,
  "availability": null
}
```

## PackAvailability

When a pack is sold during a window, in limited quantity, or as a founder pack. See [Founder packs](/docs/guides/founder-packs).

| Field | Type | Description |
|---|---|---|
| `startsAt` | string or `null` | When the sale starts (included), an ISO 8601 date in UTC. `null` when the pack is on sale from the start. |
| `endsAt` | string or `null` | When the sale ends (excluded). `null` when it has no end. |
| `remaining` | integer or `null` | The units still on sale in the environment of your key: the total stock minus the orders that are delivered, paid or pending. Never below 0. `null` for an unlimited stock. |
| `founder` | boolean | `true` for a founder pack: sold before the launch of the game, shown with a badge and a public counter. |

```json
{ "startsAt": "2026-10-01T00:00:00.000Z", "endsAt": "2026-12-01T00:00:00.000Z", "remaining": 312, "founder": true }
```

## Catalog

The active currencies, items and packs of the game. Archived and draft entries are left out.

| Field | Type | Description |
|---|---|---|
| `currencies` | array of [Currency](#currency) | The currencies. |
| `items` | array of [Item](#item) | The items. |
| `packs` | array of [Pack](#pack) | The packs. |
| `prelaunch` | boolean | `true` while the game is not launched: a shop presents purchases as pre-orders. |
| `launchAt` | string or `null` | The announced launch date of the game, an ISO 8601 date in UTC. `null` when it is not announced. |

A complete catalog is shown in [Get the catalog](/docs/api/catalog#get-the-catalog).

## Order

The purchase of a pack with real money, created by a [checkout](/docs/api/checkouts).

| Field | Type | Description |
|---|---|---|
| `id` | string | The order id. |
| `playerId` | string | The player who ordered. |
| `status` | string | Where the order stands: see [Order statuses](#order-statuses). |
| `pack` | object | The pack as it was sold: `sku` and `name` at the time of the order. |
| `amountCents` | integer | The amount in euro cents, VAT included. |
| `currency` | string | Always `EUR`. |
| `country` | string | The buyer's country (ISO 3166-1 alpha-2), which decided the VAT. |
| `vatRateBp` | integer | The VAT rate in basis points: 2100 is 21 %. |
| `vatCents` | integer | The VAT included in `amountCents`. |
| `netCents` | integer | `amountCents` minus `vatCents`. |
| `createdAt` | string | When the order was created. |
| `paidAt` | string or `null` | When the payment succeeded. |
| `fulfilledAt` | string or `null` | When the coins and items were delivered. |
| `refundedAt` | string or `null` | When the order was refunded. |
| `creatorCode` | object or `null` | The [creator code](/docs/guides/codes#creator-codes) the buyer used: `code` and `creatorName`. The bonus and commission rates are never shown. `null` when no code was used. |

```json
{
  "id": "665f1c2e8a3b4d5e6f7081b4",
  "playerId": "665f1c2e8a3b4d5e6f708192",
  "status": "fulfilled",
  "pack": { "sku": "gems-500", "name": "Bag of gems" },
  "amountCents": 499,
  "currency": "EUR",
  "country": "BE",
  "vatRateBp": 2100,
  "vatCents": 87,
  "netCents": 412,
  "createdAt": "2026-10-05T22:14:32.995Z",
  "paidAt": "2026-10-05T22:14:34.054Z",
  "fulfilledAt": "2026-10-05T22:14:34.054Z",
  "refundedAt": null,
  "creatorCode": null
}
```

### Order statuses

```text
pending → paid → fulfilled → refunded
                           → chargeback
pending → failed | canceled | expired
```

| `status` | Meaning | Final |
|---|---|---|
| `pending` | The order is created and waits for the payment. It becomes `expired` after one hour. | No |
| `paid` | The payment is confirmed and the delivery is under way. It is a step of a moment, not a state you wait in. | No |
| `fulfilled` | The payment succeeded and the coins and items are delivered. | Until a refund |
| `refunded` | The order was refunded and the coins and items were taken back. | Yes |
| `chargeback` | The cardholder disputed the payment. | Yes |
| `failed` | The payment was refused. | Yes |
| `canceled` | The order was canceled before payment. | Yes |
| `expired` | The order was not paid within one hour. | Yes |

## Player token

A short-lived credential that lets one player act on their own wallet and inventory through the [client API](/docs/api/client). Returned by [Issue a player token](/docs/api/players#issue-a-player-token) and [Get a new player token](/docs/api/client#get-a-new-player-token).

| Field | Type | Description |
|---|---|---|
| `token` | string | The token, `gc_pt_…`, sent as `Authorization: Bearer`. An opaque string: do not parse it. |
| `expiresAt` | string | When it stops working: one hour after it was issued. |

```json
{ "token": "gc_pt_…", "expiresAt": "2026-10-05T23:14:35.000Z" }
```

[Create an anonymous player](/docs/api/client#create-an-anonymous-player) returns a token with the new player and their `playerSecret`:

```json
{
  "player": {
    "id": "665f1c2e8a3b4d5e6f7081c6",
    "externalId": null,
    "kind": "hosted",
    "displayName": "Guest",
    "country": null,
    "blocked": false,
    "createdAt": "2026-10-05T22:14:41.141Z"
  },
  "playerSecret": "bD7QMp…",
  "token": "gc_pt_…",
  "expiresAt": "2026-10-05T23:14:41.000Z"
}
```

## Responses that combine objects

| Name | Returned by | Fields |
|---|---|---|
| Movement | [Grant currency](/docs/api/wallet#grant-currency), [Spend currency](/docs/api/wallet#spend-currency) | `entry` ([LedgerEntry](#ledgerentry)), `balance` ([Balance](#balance)) |
| Purchase | [Buy an item with currency](/docs/api/purchases#buy-an-item-with-currency) | `entry`, `balance`, `item` ([InventoryItem](#inventoryitem)) |
| Checkout | [Order a currency pack](/docs/api/checkouts#order-a-currency-pack) | `order` ([Order](#order)), `checkoutUrl` (string) |
| Redemption | [Redeem a gift code](/docs/api/codes#redeem-a-gift-code) | `grants` (array of `currency`, `amount`, `balance`), `items` (array of `sku`, `quantity`), `skipped` (array of `sku`, `quantity`) |
| Ledger page | [List ledger entries](/docs/api/ledger#list-ledger-entries) | `entries` (array of LedgerEntry), `nextCursor` (string or `null`) |
| Inventory | [List the inventory](/docs/api/inventory#list-the-inventory) | `items` (array of InventoryItem) |
| Inventory change | [Grant an item](/docs/api/inventory#grant-an-item), [Consume an item](/docs/api/inventory#consume-an-item) | `item` (InventoryItem) |
| Me | [Get the token player](/docs/api/client#get-the-token-player) | `player`, `wallet`, `inventory` (array of InventoryItem) |
| Anonymous player | [Create an anonymous player](/docs/api/client#create-an-anonymous-player) | `player`, `playerSecret`, `token`, `expiresAt` |

## Error

Every response that is not a success has this body, with the HTTP status of the error.

| Field | Type | Description |
|---|---|---|
| `error.code` | string | The stable code of the error: test this one. The codes are listed in [Errors](/docs/concepts/errors). |
| `error.message` | string | An English message for developers, not for your players. |
| `error.details` | object | Optional. `required` and `available` for `INSUFFICIENT_FUNDS`; `fieldErrors` for `VALIDATION_FAILED`: a field name (or `_` for the whole body) mapped to messages or codes such as `COUNTRY_NOT_SELLABLE`, `CURSOR_INVALID`, `PLAYER_REF_INVALID`, `URL_INVALID`, `URL_SCHEME_NOT_ALLOWED` or `ORIGIN_MISMATCH`. Meant to be read, not parsed. |

```json
{
  "error": {
    "code": "INSUFFICIENT_FUNDS",
    "message": "Insufficient funds",
    "details": { "required": 5000, "available": 70 }
  }
}
```
