{"openapi":"3.1.0","info":{"title":"GameCoin API","version":"1.0.0","summary":"The bank of your game: virtual currency, inventory and sales.","description":"GameCoin keeps the virtual currency, the inventory and the sales of your game. Two APIs share the base URL:\n\n- **Server API** (your game backend): `Authorization: Bearer gc_sk_…` (secret key). No CORS.\n- **Client API** (`/client/**`, browser or game client): `X-GameCoin-Key: gc_pk_…` (publishable key); player routes also need `Authorization: Bearer gc_pt_…` (player token). CORS open; it never credits anything, except free coins and items from a gift code.\n\nThe key decides the game and the environment (`test` or `live`); nothing in a request can change that.\n\n**Players.** `{player}` is a GameCoin player id, or `ext:<your id>` URL-encoded exactly once. With a secret key a `PUT` or `POST` on an unknown `ext:` player creates it; a `GET` answers `404`.\n\n**Idempotency.** Operations marked with an `Idempotency-Key` header require it. Same key and same request: the original response with `Idempotent-Replayed: true`. Same key and another request: `409 IDEMPOTENCY_CONFLICT`. Keys are kept 30 days. A replayed checkout returns the order in its current state.\n\n**Requests.** JSON in, JSON out; bodies are strict (an unknown field is `400 VALIDATION_FAILED`) and at most 64 KiB. Timestamps are ISO 8601 UTC strings; amounts are positive integers. Every response carries `Cache-Control: no-store`.\n\n**Lists.** `?limit=` (1 to 100, default 20) and `?cursor=`; the response carries `nextCursor` (or `null`).\n\n**Rate limits.** Server API: 600 requests per minute per secret key. Client API: 120 per minute per player (with a token) or per IP (without); anonymous player creation: 30 per hour per IP; e-mail codes: 3 per hour per address. A `429` carries `Retry-After` (seconds).\n\n**Errors.** `{ \"error\": { \"code\", \"message\", \"details\"? } }` with the codes of the `ErrorCode` schema. Requests are processed in this order: authentication, rate limit, idempotency key, body validation."},"servers":[{"url":"https://gamecoin.apilow.com/api/v1","description":"This GameCoin instance"}],"tags":[{"name":"Catalog","description":"Currencies, items and packs of the game."},{"name":"Players","description":"Create, read and issue tokens for players."},{"name":"Wallet","description":"Balances, grants and spends."},{"name":"Ledger","description":"Immutable history of every wallet change."},{"name":"Inventory","description":"Items owned by a player."},{"name":"Purchases","description":"Buy an item with a currency."},{"name":"Checkouts","description":"Sell a currency pack for real money (hosted payment page)."},{"name":"Orders","description":"Read and refund orders."},{"name":"Codes","description":"Gift codes: free coins and items for the player who redeems one."},{"name":"Client catalog","description":"Client API: the catalog, with a publishable key."},{"name":"Client players","description":"Client API: anonymous players and their tokens."},{"name":"Client me","description":"Client API: what the token player can read and do."}],"paths":{"/catalog":{"get":{"operationId":"getCatalog","summary":"Get the catalog","description":"Active currencies, items and packs of the game.","tags":["Catalog"],"security":[{"secretKey":[]}],"responses":{"200":{"description":"The catalog.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Catalog"},"example":{"currencies":[{"code":"gems","name":"Gems","purchasable":true},{"code":"gold","name":"Gold","purchasable":false}],"items":[{"sku":"potion","name":"Potion","description":null,"type":"consumable","price":{"currency":"gems","amount":20},"maxOwned":null,"clientPurchasable":true,"metadata":{}},{"sku":"fire-sword","name":"Fire sword","description":"A blade that never cools.","type":"durable","price":{"currency":"gems","amount":300},"maxOwned":1,"clientPurchasable":true,"metadata":{"rarity":"epic"}}],"packs":[{"sku":"gems-500","name":"500 gems","description":null,"priceCents":499,"currency":"EUR","grants":[{"currency":"gems","amount":500,"bonus":0}],"items":[],"badge":null,"maxPerPlayer":null,"availability":null},{"sku":"founder-pack","name":"Founder pack","description":"Limited to the first 500 players.","priceCents":1999,"currency":"EUR","grants":[{"currency":"gems","amount":2000,"bonus":500}],"items":[{"sku":"fire-sword","quantity":1}],"badge":null,"maxPerPlayer":1,"availability":{"startsAt":"2026-10-01T00:00:00.000Z","endsAt":"2026-12-01T00:00:00.000Z","remaining":312,"founder":true}}],"prelaunch":true,"launchAt":"2026-12-01T00:00:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/players/{player}":{"parameters":[{"$ref":"#/components/parameters/Player"}],"put":{"operationId":"upsertPlayer","summary":"Create or update a player","description":"Creates the `ext:` player when it does not exist, otherwise updates the profile fields sent. A GameCoin id that does not exist answers `404`.","tags":["Players"],"security":[{"secretKey":[]}],"requestBody":{"required":false,"description":"All fields are optional; an empty body means `{}`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePlayerRequest"},"example":{"displayName":"Alice","country":"BE"}}}},"responses":{"200":{"description":"The player.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Player"},"example":{"id":"665f1c2e8a3b4d5e6f708192","externalId":"user-42","kind":"external","displayName":"Alice","country":"BE","blocked":false,"createdAt":"2026-10-05T12:00:00.000Z"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"get":{"operationId":"getPlayer","summary":"Get a player","tags":["Players"],"security":[{"secretKey":[]}],"responses":{"200":{"description":"The player.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Player"},"example":{"id":"665f1c2e8a3b4d5e6f708192","externalId":"user-42","kind":"external","displayName":"Alice","country":"BE","blocked":false,"createdAt":"2026-10-05T12:00:00.000Z"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/players/{player}/tokens":{"parameters":[{"$ref":"#/components/parameters/Player"}],"post":{"operationId":"createPlayerToken","summary":"Issue a player token","description":"A one-hour token to hand to your game client, for the client API. Creates the `ext:` player when missing.","tags":["Players"],"security":[{"secretKey":[]}],"responses":{"200":{"description":"The token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayerToken"},"example":{"token":"gc_pt_<payload>.<signature>","expiresAt":"2026-10-05T13:00:00.000Z"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/players/{player}/wallet":{"parameters":[{"$ref":"#/components/parameters/Player"}],"get":{"operationId":"getWallet","summary":"Get a wallet","tags":["Wallet"],"security":[{"secretKey":[]}],"responses":{"200":{"description":"One balance per active currency.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Wallet"},"example":{"playerId":"665f1c2e8a3b4d5e6f708192","balances":[{"currency":"gems","paid":0,"bonus":100,"total":100},{"currency":"gold","paid":0,"bonus":0,"total":0}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/players/{player}/wallet/grant":{"parameters":[{"$ref":"#/components/parameters/Player"}],"post":{"operationId":"grantCurrency","summary":"Grant currency","description":"Credits the `bonus` bucket of the wallet. Creates the `ext:` player when missing.","tags":["Wallet"],"security":[{"secretKey":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletMovementRequest"},"example":{"currency":"gems","amount":100,"reason":"level_up","metadata":{"level":"5"}}}}},"responses":{"200":{"description":"The ledger entry and the new balance.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Movement"},"example":{"entry":{"id":"665f1c2e8a3b4d5e6f7081a3","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-05T12:00:00.000Z"},"balance":{"currency":"gems","paid":0,"bonus":100,"total":100}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/players/{player}/wallet/spend":{"parameters":[{"$ref":"#/components/parameters/Player"}],"post":{"operationId":"spendCurrency","summary":"Spend currency","description":"Debits the wallet, `bonus` first. `409 INSUFFICIENT_FUNDS` when the balance is too low.","tags":["Wallet"],"security":[{"secretKey":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletMovementRequest"},"example":{"currency":"gems","amount":30,"reason":"revive"}}}},"responses":{"200":{"description":"The ledger entry and the new balance.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Movement"},"example":{"entry":{"id":"665f1c2e8a3b4d5e6f7081a4","type":"spend","currency":"gems","paidDelta":0,"bonusDelta":-30,"balanceAfter":{"paid":0,"bonus":70,"total":70},"reason":"revive","metadata":{},"ref":{},"createdAt":"2026-10-05T13:00:00.000Z"},"balance":{"currency":"gems","paid":0,"bonus":70,"total":70}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/players/{player}/ledger":{"parameters":[{"$ref":"#/components/parameters/Player"}],"get":{"operationId":"listLedger","summary":"List ledger entries","description":"Newest first, paginated with `limit` and `cursor`.","tags":["Ledger"],"security":[{"secretKey":[]}],"parameters":[{"$ref":"#/components/parameters/LedgerCurrency"},{"$ref":"#/components/parameters/PageLimit"},{"$ref":"#/components/parameters/PageCursor"}],"responses":{"200":{"description":"A page of entries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LedgerPage"},"example":{"entries":[{"id":"665f1c2e8a3b4d5e6f7081a4","type":"spend","currency":"gems","paidDelta":0,"bonusDelta":-30,"balanceAfter":{"paid":0,"bonus":70,"total":70},"reason":"revive","metadata":{},"ref":{},"createdAt":"2026-10-05T13:00:00.000Z"},{"id":"665f1c2e8a3b4d5e6f7081a3","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-05T12:00:00.000Z"}],"nextCursor":null}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/players/{player}/inventory":{"parameters":[{"$ref":"#/components/parameters/Player"}],"get":{"operationId":"listInventory","summary":"List the inventory","description":"Owned items only (quantity above 0).","tags":["Inventory"],"security":[{"secretKey":[]}],"responses":{"200":{"description":"The owned items.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryList"},"example":{"items":[{"sku":"potion","quantity":2,"updatedAt":"2026-10-05T12:00:00.000Z"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/players/{player}/inventory/grant":{"parameters":[{"$ref":"#/components/parameters/Player"}],"post":{"operationId":"grantItem","summary":"Grant an item","description":"Adds units to the inventory; `409 LIMIT_REACHED` beyond the item's `maxOwned`. Creates the `ext:` player when missing.","tags":["Inventory"],"security":[{"secretKey":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemQuantityRequest"},"example":{"sku":"potion","quantity":2}}}},"responses":{"200":{"description":"The item with its new quantity.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryChange"},"example":{"item":{"sku":"potion","quantity":2,"updatedAt":"2026-10-05T12:00:00.000Z"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/players/{player}/inventory/consume":{"parameters":[{"$ref":"#/components/parameters/Player"}],"post":{"operationId":"consumeItem","summary":"Consume an item","description":"Removes units of a `consumable` item. `409 INSUFFICIENT_FUNDS` when the player owns fewer; `409 CONFLICT` for a durable item. The item is returned even when it reaches 0.","tags":["Inventory"],"security":[{"secretKey":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemQuantityRequest"},"example":{"sku":"potion"}}}},"responses":{"200":{"description":"The item with its new quantity.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryChange"},"example":{"item":{"sku":"potion","quantity":1,"updatedAt":"2026-10-05T13:00:00.000Z"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/players/{player}/purchases":{"parameters":[{"$ref":"#/components/parameters/Player"}],"post":{"operationId":"createPurchase","summary":"Buy an item with currency","description":"Debits the item's price in its currency and adds the item to the inventory, atomically.","tags":["Purchases"],"security":[{"secretKey":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemQuantityRequest"},"example":{"sku":"potion","quantity":1}}}},"responses":{"200":{"description":"The debit, the new balance and the item.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Purchase"},"example":{"entry":{"id":"665f1c2e8a3b4d5e6f7081a4","type":"spend","currency":"gems","paidDelta":0,"bonusDelta":-20,"balanceAfter":{"paid":0,"bonus":80,"total":80},"reason":null,"metadata":{},"ref":{"itemSku":"potion"},"createdAt":"2026-10-05T13:00:00.000Z"},"balance":{"currency":"gems","paid":0,"bonus":80,"total":80},"item":{"sku":"potion","quantity":2,"updatedAt":"2026-10-05T12:00:00.000Z"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/players/{player}/checkouts":{"parameters":[{"$ref":"#/components/parameters/Player"}],"post":{"operationId":"createCheckout","summary":"Order a currency pack","description":"Creates an order for a pack and returns the hosted payment page. A pack outside its sale window answers `409 PACK_NOT_AVAILABLE`, a pack without stock left `409 PACK_SOLD_OUT`; a new order holds one unit of the stock until it expires, fails or is canceled. `201` for a new order; a replay answers `200` with the order in its current state. Creates the `ext:` player when missing.","tags":["Checkouts"],"security":[{"secretKey":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerCheckoutRequest"},"example":{"packSku":"gems-500","successUrl":"https://example.com/shop/thanks","cancelUrl":"https://example.com/shop","country":"BE","locale":"nl"}}}},"responses":{"200":{"description":"Replay of an earlier request (`Idempotent-Replayed: true`): the order in its current state.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"},"example":{"order":{"id":"665f1c2e8a3b4d5e6f7081b4","playerId":"665f1c2e8a3b4d5e6f708192","status":"pending","pack":{"sku":"gems-500","name":"500 gems"},"amountCents":499,"currency":"EUR","country":"BE","vatRateBp":2100,"vatCents":87,"netCents":412,"createdAt":"2026-10-05T12:00:00.000Z","paidAt":null,"fulfilledAt":null,"refundedAt":null,"creatorCode":null},"checkoutUrl":"https://gamecoin.apilow.com/pay/665f1c2e8a3b4d5e6f7081b4?t=signed-token"}}}},"201":{"description":"New order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"},"example":{"order":{"id":"665f1c2e8a3b4d5e6f7081b4","playerId":"665f1c2e8a3b4d5e6f708192","status":"pending","pack":{"sku":"gems-500","name":"500 gems"},"amountCents":499,"currency":"EUR","country":"BE","vatRateBp":2100,"vatCents":87,"netCents":412,"createdAt":"2026-10-05T12:00:00.000Z","paidAt":null,"fulfilledAt":null,"refundedAt":null,"creatorCode":null},"checkoutUrl":"https://gamecoin.apilow.com/pay/665f1c2e8a3b4d5e6f7081b4?t=signed-token"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/PaymentFailed"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/players/{player}/codes/redeem":{"parameters":[{"$ref":"#/components/parameters/Player"}],"post":{"operationId":"redeemCode","summary":"Redeem a gift code","description":"The player uses a gift code: free coins (`bonus` bucket, ledger type `gift_code`) and items. A code works once per player and up to its limit of uses. Every refusal (unknown, expired, used up, disabled, already used by this player) is the same `400 VALIDATION_FAILED` with `fieldErrors.code = [\"CODE_INVALID\"]`. Limited to 10 attempts per 15 minutes per player. Creates the `ext:` player when missing.","tags":["Codes"],"security":[{"secretKey":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedeemCodeRequest"},"example":{"code":"SUMMER-K7M2Q-XH4NP"}}}},"responses":{"200":{"description":"What the code gave.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Redemption"},"example":{"grants":[{"currency":"gems","amount":100,"balance":{"currency":"gems","paid":0,"bonus":100,"total":100}}],"items":[{"sku":"potion","quantity":2}],"skipped":[]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/orders/{orderId}":{"parameters":[{"$ref":"#/components/parameters/OrderId"}],"get":{"operationId":"getOrder","summary":"Get an order","tags":["Orders"],"security":[{"secretKey":[]}],"responses":{"200":{"description":"The order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"},"example":{"id":"665f1c2e8a3b4d5e6f7081b4","playerId":"665f1c2e8a3b4d5e6f708192","status":"pending","pack":{"sku":"gems-500","name":"500 gems"},"amountCents":499,"currency":"EUR","country":"BE","vatRateBp":2100,"vatCents":87,"netCents":412,"createdAt":"2026-10-05T12:00:00.000Z","paidAt":null,"fulfilledAt":null,"refundedAt":null,"creatorCode":null}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/orders/{orderId}/refund":{"parameters":[{"$ref":"#/components/parameters/OrderId"}],"post":{"operationId":"refundOrder","summary":"Refund an order","description":"Refunds a fulfilled order and takes the coins and items back. Not idempotent by key: after a network error or a `5xx`, call `GET /orders/{orderId}` before retrying. `409 CONFLICT` for an order that cannot be refunded (e.g. unpaid).","tags":["Orders"],"security":[{"secretKey":[]}],"requestBody":{"required":false,"description":"Optional; an empty body means `{}`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundRequest"},"example":{"reason":"Player request"}}}},"responses":{"200":{"description":"The refunded order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"},"example":{"id":"665f1c2e8a3b4d5e6f7081b4","playerId":"665f1c2e8a3b4d5e6f708192","status":"refunded","pack":{"sku":"gems-500","name":"500 gems"},"amountCents":499,"currency":"EUR","country":"BE","vatRateBp":2100,"vatCents":87,"netCents":412,"createdAt":"2026-10-05T12:00:00.000Z","paidAt":"2026-10-05T12:00:00.000Z","fulfilledAt":"2026-10-05T12:00:00.000Z","refundedAt":"2026-10-05T13:00:00.000Z","creatorCode":null}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/PaymentFailed"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/catalog":{"get":{"operationId":"getClientCatalog","summary":"Get the catalog","tags":["Client catalog"],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"The catalog.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Catalog"},"example":{"currencies":[{"code":"gems","name":"Gems","purchasable":true},{"code":"gold","name":"Gold","purchasable":false}],"items":[{"sku":"potion","name":"Potion","description":null,"type":"consumable","price":{"currency":"gems","amount":20},"maxOwned":null,"clientPurchasable":true,"metadata":{}},{"sku":"fire-sword","name":"Fire sword","description":"A blade that never cools.","type":"durable","price":{"currency":"gems","amount":300},"maxOwned":1,"clientPurchasable":true,"metadata":{"rarity":"epic"}}],"packs":[{"sku":"gems-500","name":"500 gems","description":null,"priceCents":499,"currency":"EUR","grants":[{"currency":"gems","amount":500,"bonus":0}],"items":[],"badge":null,"maxPerPlayer":null,"availability":null},{"sku":"founder-pack","name":"Founder pack","description":"Limited to the first 500 players.","priceCents":1999,"currency":"EUR","grants":[{"currency":"gems","amount":2000,"bonus":500}],"items":[{"sku":"fire-sword","quantity":1}],"badge":null,"maxPerPlayer":1,"availability":{"startsAt":"2026-10-01T00:00:00.000Z","endsAt":"2026-12-01T00:00:00.000Z","remaining":312,"founder":true}}],"prelaunch":true,"launchAt":"2026-12-01T00:00:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/players":{"post":{"operationId":"createAnonymousPlayer","summary":"Create an anonymous player","description":"For games without a backend. Returns the player, a `playerSecret` (shown once; keep it on the device) and a first token. `403 FORBIDDEN` when the game disables anonymous players. Limited to 30 per hour per IP.","tags":["Client players"],"security":[{"publishableKey":[]}],"requestBody":{"required":false,"description":"Optional; an empty body means `{}`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAnonymousPlayerRequest"},"example":{"displayName":"Guest"}}}},"responses":{"201":{"description":"The new player and their credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnonymousPlayer"},"example":{"player":{"id":"665f1c2e8a3b4d5e6f708192","externalId":null,"kind":"hosted","displayName":"Guest","country":null,"blocked":false,"createdAt":"2026-10-05T12:00:00.000Z"},"playerSecret":"<player secret>","token":"gc_pt_<payload>.<signature>","expiresAt":"2026-10-05T13:00:00.000Z"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/players/token":{"post":{"operationId":"createClientPlayerToken","summary":"Get a new player token","description":"Exchanges the secret of an anonymous player for a token. A wrong secret, an unknown player or a player that is not anonymous answers `401`.","tags":["Client players"],"security":[{"publishableKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayerTokenRequest"},"example":{"playerId":"665f1c2e8a3b4d5e6f708192","playerSecret":"<player secret>"}}}},"responses":{"200":{"description":"The token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayerToken"},"example":{"token":"gc_pt_<payload>.<signature>","expiresAt":"2026-10-05T13:00:00.000Z"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/me":{"get":{"operationId":"getMe","summary":"Get the token player","description":"The player, their wallet and their inventory in one call.","tags":["Client me"],"security":[{"publishableKey":[],"playerToken":[]}],"responses":{"200":{"description":"The player, the wallet and the owned items.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"},"example":{"player":{"id":"665f1c2e8a3b4d5e6f708192","externalId":"user-42","kind":"external","displayName":"Alice","country":"BE","blocked":false,"createdAt":"2026-10-05T12:00:00.000Z"},"wallet":{"playerId":"665f1c2e8a3b4d5e6f708192","balances":[{"currency":"gems","paid":0,"bonus":100,"total":100},{"currency":"gold","paid":0,"bonus":0,"total":0}]},"inventory":[{"sku":"potion","quantity":2,"updatedAt":"2026-10-05T12:00:00.000Z"}]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/me/wallet/spend":{"post":{"operationId":"spendClient","summary":"Spend currency","tags":["Client me"],"security":[{"publishableKey":[],"playerToken":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientSpendRequest"},"example":{"currency":"gems","amount":30,"reason":"revive"}}}},"responses":{"200":{"description":"The ledger entry and the new balance.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Movement"},"example":{"entry":{"id":"665f1c2e8a3b4d5e6f7081a4","type":"spend","currency":"gems","paidDelta":0,"bonusDelta":-30,"balanceAfter":{"paid":0,"bonus":70,"total":70},"reason":"revive","metadata":{},"ref":{},"createdAt":"2026-10-05T13:00:00.000Z"},"balance":{"currency":"gems","paid":0,"bonus":70,"total":70}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/me/purchases":{"post":{"operationId":"createClientPurchase","summary":"Buy an item with currency","description":"Only items flagged `clientPurchasable`; `403 FORBIDDEN` otherwise.","tags":["Client me"],"security":[{"publishableKey":[],"playerToken":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemQuantityRequest"},"example":{"sku":"potion","quantity":1}}}},"responses":{"200":{"description":"The debit, the new balance and the item.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Purchase"},"example":{"entry":{"id":"665f1c2e8a3b4d5e6f7081a4","type":"spend","currency":"gems","paidDelta":0,"bonusDelta":-20,"balanceAfter":{"paid":0,"bonus":80,"total":80},"reason":null,"metadata":{},"ref":{"itemSku":"potion"},"createdAt":"2026-10-05T13:00:00.000Z"},"balance":{"currency":"gems","paid":0,"bonus":80,"total":80},"item":{"sku":"potion","quantity":2,"updatedAt":"2026-10-05T12:00:00.000Z"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/me/inventory/consume":{"post":{"operationId":"consumeClientItem","summary":"Consume an item","tags":["Client me"],"security":[{"publishableKey":[],"playerToken":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemQuantityRequest"},"example":{"sku":"potion"}}}},"responses":{"200":{"description":"The item with its new quantity.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryChange"},"example":{"item":{"sku":"potion","quantity":1,"updatedAt":"2026-10-05T13:00:00.000Z"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/me/checkouts":{"post":{"operationId":"createClientCheckout","summary":"Order a currency pack","description":"`successUrl` and `cancelUrl` must have the same origin as the request's `Origin` header, so a non-browser client cannot pass return URLs. `country` is server-only. A pack outside its sale window answers `409 PACK_NOT_AVAILABLE`, a pack without stock left `409 PACK_SOLD_OUT`.","tags":["Client me"],"security":[{"publishableKey":[],"playerToken":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientCheckoutRequest"},"example":{"packSku":"gems-500","successUrl":"https://example.com/shop/thanks"}}}},"responses":{"200":{"description":"Replay of an earlier request (`Idempotent-Replayed: true`): the order in its current state.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"},"example":{"order":{"id":"665f1c2e8a3b4d5e6f7081b4","playerId":"665f1c2e8a3b4d5e6f708192","status":"pending","pack":{"sku":"gems-500","name":"500 gems"},"amountCents":499,"currency":"EUR","country":"BE","vatRateBp":2100,"vatCents":87,"netCents":412,"createdAt":"2026-10-05T12:00:00.000Z","paidAt":null,"fulfilledAt":null,"refundedAt":null,"creatorCode":null},"checkoutUrl":"https://gamecoin.apilow.com/pay/665f1c2e8a3b4d5e6f7081b4?t=signed-token"}}}},"201":{"description":"New order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"},"example":{"order":{"id":"665f1c2e8a3b4d5e6f7081b4","playerId":"665f1c2e8a3b4d5e6f708192","status":"pending","pack":{"sku":"gems-500","name":"500 gems"},"amountCents":499,"currency":"EUR","country":"BE","vatRateBp":2100,"vatCents":87,"netCents":412,"createdAt":"2026-10-05T12:00:00.000Z","paidAt":null,"fulfilledAt":null,"refundedAt":null,"creatorCode":null},"checkoutUrl":"https://gamecoin.apilow.com/pay/665f1c2e8a3b4d5e6f7081b4?t=signed-token"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/PaymentFailed"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/me/codes/redeem":{"post":{"operationId":"redeemClientCode","summary":"Redeem a gift code","description":"The token player uses a gift code: free coins (`bonus` bucket) and items. It is the only client route that adds coins, and only free ones: a code is issued by the studio, has a limited number of uses and works once per player. Every refusal is the same `400 VALIDATION_FAILED` with `fieldErrors.code = [\"CODE_INVALID\"]`. Limited to 10 attempts per 15 minutes per player and 30 per IP.","tags":["Client me"],"security":[{"publishableKey":[],"playerToken":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedeemCodeRequest"},"example":{"code":"SUMMER-K7M2Q-XH4NP"}}}},"responses":{"200":{"description":"What the code gave.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Redemption"},"example":{"grants":[{"currency":"gems","amount":100,"balance":{"currency":"gems","paid":0,"bonus":100,"total":100}}],"items":[{"sku":"potion","quantity":2}],"skipped":[]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/me/orders/{orderId}":{"parameters":[{"$ref":"#/components/parameters/OrderId"}],"get":{"operationId":"getClientOrder","summary":"Get an order of the token player","description":"An order of another player answers `404`.","tags":["Client me"],"security":[{"publishableKey":[],"playerToken":[]}],"responses":{"200":{"description":"The order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"},"example":{"id":"665f1c2e8a3b4d5e6f7081b4","playerId":"665f1c2e8a3b4d5e6f708192","status":"pending","pack":{"sku":"gems-500","name":"500 gems"},"amountCents":499,"currency":"EUR","country":"BE","vatRateBp":2100,"vatCents":87,"netCents":412,"createdAt":"2026-10-05T12:00:00.000Z","paidAt":null,"fulfilledAt":null,"refundedAt":null,"creatorCode":null}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/me/email":{"post":{"operationId":"requestMeEmailCode","summary":"Send a sign-in code to an e-mail address","description":"For anonymous (hosted) players; `403 FORBIDDEN` for `ext:` players. Sends a six-digit code valid 10 minutes (one use, 5 tries). The answer is neutral. Limits: 3 per hour per address, 10 per hour per player and per IP.","tags":["Client me"],"security":[{"publishableKey":[],"playerToken":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestEmailCodeRequest"},"example":{"email":"alice@example.com","locale":"en"}}}},"responses":{"200":{"description":"Code requested.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCodeSent"},"example":{"sent":true,"expiresInSeconds":600}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/me/email/verify":{"post":{"operationId":"verifyMeEmailCode","summary":"Link an e-mail address, or get the linked player back","description":"Links the address to the token player, or, when it is already linked to another player, switches the session to that player (`merged: true`, a new `playerSecret`; no wallet is merged). A wrong, expired, used-up or unknown code always answers `400 VALIDATION_FAILED` with `fieldErrors.code = [\"CODE_INVALID\"]`.","tags":["Client me"],"security":[{"publishableKey":[],"playerToken":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyEmailCodeRequest"},"example":{"email":"alice@example.com","code":"042917"}}}},"responses":{"200":{"description":"The session player.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerification"},"example":{"token":"gc_pt_<payload>.<signature>","expiresAt":"2026-10-05T13:00:00.000Z","player":{"id":"665f1c2e8a3b4d5e6f708192","externalId":null,"kind":"hosted","displayName":"Alice","country":"BE","blocked":false,"createdAt":"2026-10-05T12:00:00.000Z"},"merged":false,"playerSecret":null}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/sign-in/email":{"post":{"operationId":"requestSignInEmailCode","summary":"Send a sign-in code (new device)","description":"Without a player token. Same code, same neutral answer and same limits as `POST /client/me/email`.","tags":["Client players"],"security":[{"publishableKey":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestEmailCodeRequest"},"example":{"email":"alice@example.com","locale":"en"}}}},"responses":{"200":{"description":"Code requested.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCodeSent"},"example":{"sent":true,"expiresInSeconds":600}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/client/sign-in/email/verify":{"post":{"operationId":"verifySignInEmailCode","summary":"Get the player linked to an e-mail address (new device)","description":"Returns a token and a new `playerSecret` for the player linked to the address. `404 NOT_FOUND` when a valid code proves the address but no player is linked to it.","tags":["Client players"],"security":[{"publishableKey":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyEmailCodeRequest"},"example":{"email":"alice@example.com","code":"042917"}}}},"responses":{"200":{"description":"The linked player and a new secret.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerification"},"example":{"token":"gc_pt_<payload>.<signature>","expiresAt":"2026-10-05T13:00:00.000Z","player":{"id":"665f1c2e8a3b4d5e6f708192","externalId":null,"kind":"hosted","displayName":"Alice","country":"BE","blocked":false,"createdAt":"2026-10-05T12:00:00.000Z"},"merged":false,"playerSecret":"<player secret>"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}},"components":{"schemas":{"Player":{"type":"object","description":"A player of your game.","properties":{"id":{"type":"string","description":"GameCoin player id (24 hexadecimal characters)."},"externalId":{"type":["string","null"],"description":"Your own id for this player (`ext:` players); `null` for anonymous players."},"kind":{"type":"string","description":"`external`: created by your backend; `hosted`: anonymous player created by the client API.","enum":["external","hosted"]},"displayName":{"type":["string","null"],"description":"Name shown in your game."},"country":{"type":["string","null"],"description":"ISO 3166-1 alpha-2 country code."},"blocked":{"type":"boolean","description":"A blocked player is refused on every client route and on wallet or inventory writes."},"createdAt":{"type":"string","format":"date-time","description":"Creation time (ISO 8601, UTC)."}},"required":["id","externalId","kind","displayName","country","blocked","createdAt"]},"Balance":{"type":"object","description":"A player's balance in one currency, split into its two buckets.","properties":{"currency":{"type":"string","description":"Currency code."},"paid":{"type":"integer","description":"Units bought with real money (refundable)."},"bonus":{"type":"integer","description":"Units granted or earned for free."},"total":{"type":"integer","description":"`paid` + `bonus`."}},"required":["currency","paid","bonus","total"]},"Wallet":{"type":"object","description":"All the balances of a player.","properties":{"playerId":{"type":"string","description":"Player id."},"balances":{"type":"array","description":"One line per active currency, zeros included.","items":{"$ref":"#/components/schemas/Balance"}}},"required":["playerId","balances"]},"LedgerEntry":{"type":"object","description":"One immutable line of a player's ledger.","properties":{"id":{"type":"string","description":"Entry id; ids sort by creation time."},"type":{"type":"string","description":"What produced the entry.","enum":["purchase_credit","grant","spend","refund_clawback","chargeback_clawback","adjustment","gift_code"]},"currency":{"type":"string","description":"Currency code."},"paidDelta":{"type":"integer","description":"Change of the `paid` bucket."},"bonusDelta":{"type":"integer","description":"Change of the `bonus` bucket."},"balanceAfter":{"type":"object","description":"Balance right after the entry.","properties":{"paid":{"type":"integer","description":"`paid` after the entry."},"bonus":{"type":"integer","description":"`bonus` after the entry."},"total":{"type":"integer","description":"`paid` + `bonus` after the entry."}},"required":["paid","bonus","total"]},"reason":{"type":["string","null"],"description":"The `reason` you passed, if any."},"metadata":{"type":"object","description":"The `metadata` you passed, if any.","additionalProperties":{"type":"string"}},"ref":{"type":"object","description":"What the entry relates to; empty when nothing.","properties":{"orderId":{"type":"string","description":"Order that produced the entry."},"itemSku":{"type":"string","description":"Item bought with the entry."}}},"createdAt":{"type":"string","format":"date-time","description":"Creation time (ISO 8601, UTC)."}},"required":["id","type","currency","paidDelta","bonusDelta","balanceAfter","reason","metadata","ref","createdAt"]},"InventoryItem":{"type":"object","description":"An item owned by a player.","properties":{"sku":{"type":"string","description":"Item sku."},"quantity":{"type":"integer","description":"Units owned.","minimum":0},"updatedAt":{"type":"string","format":"date-time","description":"Last change (ISO 8601, UTC)."}},"required":["sku","quantity","updatedAt"]},"Currency":{"type":"object","description":"A currency of the game.","properties":{"code":{"type":"string","description":"Currency code, e.g. `gems`."},"name":{"type":"string","description":"Display name."},"purchasable":{"type":"boolean","description":"True when packs can sell this currency for real money."}},"required":["code","name","purchasable"]},"ItemPrice":{"type":"object","description":"Price of an item in a currency.","properties":{"currency":{"type":"string","description":"Currency code."},"amount":{"type":"integer","description":"Amount in that currency.","minimum":1}},"required":["currency","amount"]},"Item":{"type":"object","description":"An item of the game.","properties":{"sku":{"type":"string","description":"Item sku."},"name":{"type":"string","description":"Display name."},"description":{"type":["string","null"],"description":"Display description."},"type":{"type":"string","description":"`consumable` items can be consumed; `durable` items cannot.","enum":["consumable","durable"]},"price":{"anyOf":[{"$ref":"#/components/schemas/ItemPrice"},{"type":"null"}],"description":"Price in a currency; `null` when the item cannot be bought with currency."},"maxOwned":{"type":["integer","null"],"description":"Most units a player can own; `null` for no limit.","minimum":1},"clientPurchasable":{"type":"boolean","description":"True when the client API may sell this item."},"metadata":{"type":"object","description":"Free key/value strings set in the dashboard.","additionalProperties":{"type":"string"}}},"required":["sku","name","description","type","price","maxOwned","clientPurchasable","metadata"]},"PackGrant":{"type":"object","description":"Currency credited by a pack.","properties":{"currency":{"type":"string","description":"Currency code."},"amount":{"type":"integer","description":"Units credited to the `paid` bucket."},"bonus":{"type":"integer","description":"Extra units credited to the `bonus` bucket."}},"required":["currency","amount","bonus"]},"PackItem":{"type":"object","description":"Item granted by a pack.","properties":{"sku":{"type":"string","description":"Item sku."},"quantity":{"type":"integer","description":"Units granted.","minimum":1}},"required":["sku","quantity"]},"Pack":{"type":"object","description":"A pack sold for real money.","properties":{"sku":{"type":"string","description":"Pack sku."},"name":{"type":"string","description":"Display name."},"description":{"type":["string","null"],"description":"Display description."},"priceCents":{"type":"integer","description":"Price in euro cents, VAT included.","minimum":0},"currency":{"type":"string","description":"Always `EUR`.","enum":["EUR"]},"grants":{"type":"array","description":"Currencies credited when the order is paid.","items":{"$ref":"#/components/schemas/PackGrant"}},"items":{"type":"array","description":"Items granted when the order is paid.","items":{"$ref":"#/components/schemas/PackItem"}},"badge":{"type":["string","null"],"description":"Short label such as \"Best value\"."},"maxPerPlayer":{"type":["integer","null"],"description":"Most orders per player; `null` for no limit.","minimum":1},"availability":{"anyOf":[{"$ref":"#/components/schemas/PackAvailability"},{"type":"null"}],"description":"Sale window, remaining stock and founder flag; `null` for an ordinary pack, always on sale without limit."}},"required":["sku","name","description","priceCents","currency","grants","items","badge","maxPerPlayer","availability"]},"PackAvailability":{"type":"object","description":"Availability of a pack. Outside the window an order is refused with `409 PACK_NOT_AVAILABLE`; without stock left, with `409 PACK_SOLD_OUT`. The catalog lists scheduled, ended and sold-out packs too: your shop decides what to show.","properties":{"startsAt":{"type":["string","null"],"format":"date-time","description":"Start of the sale window (included); `null` when the pack is on sale from the start."},"endsAt":{"type":["string","null"],"format":"date-time","description":"End of the sale window (excluded); `null` when the sale has no end."},"remaining":{"type":["integer","null"],"description":"Units still on sale in the environment of the key: total stock minus delivered, paid and pending orders (a pending order holds its unit until it expires, fails or is canceled). Never below 0. `null` for an unlimited stock.","minimum":0},"founder":{"type":"boolean","description":"Founder pack: sold before the launch of the game, shown with a badge and a public counter."}},"required":["startsAt","endsAt","remaining","founder"]},"Catalog":{"type":"object","description":"Active currencies, items and packs of the game (archived and draft entries are left out).","properties":{"currencies":{"type":"array","items":{"$ref":"#/components/schemas/Currency"}},"items":{"type":"array","items":{"$ref":"#/components/schemas/Item"}},"packs":{"type":"array","items":{"$ref":"#/components/schemas/Pack"}},"prelaunch":{"type":"boolean","description":"The game is not launched yet: a shop presents purchases as pre-orders."},"launchAt":{"type":["string","null"],"format":"date-time","description":"Announced launch date of the game; `null` when not announced."}},"required":["currencies","items","packs","prelaunch","launchAt"]},"Order":{"type":"object","description":"An order of a currency pack.","properties":{"id":{"type":"string","description":"Order id."},"playerId":{"type":"string","description":"Player who ordered."},"status":{"type":"string","description":"`pending` until paid, then `fulfilled` once coins and items are delivered; `refunded`, `chargeback`, `failed`, `canceled` and `expired` are final.","enum":["pending","paid","fulfilled","refunded","chargeback","failed","canceled","expired"]},"pack":{"type":"object","description":"The pack, as sold.","properties":{"sku":{"type":"string","description":"Pack sku."},"name":{"type":"string","description":"Pack name at the time of the order."}},"required":["sku","name"]},"amountCents":{"type":"integer","description":"Amount paid in euro cents, VAT included."},"currency":{"type":"string","description":"Always `EUR`.","enum":["EUR"]},"country":{"type":"string","description":"Buyer country (ISO alpha-2) used for the VAT."},"vatRateBp":{"type":"integer","description":"VAT rate in basis points (2100 = 21 %)."},"vatCents":{"type":"integer","description":"VAT included in `amountCents`."},"netCents":{"type":"integer","description":"`amountCents` minus `vatCents`."},"createdAt":{"type":"string","format":"date-time","description":"Creation time (ISO 8601, UTC)."},"paidAt":{"type":["string","null"],"format":"date-time","description":"When the payment succeeded."},"fulfilledAt":{"type":["string","null"],"format":"date-time","description":"When coins and items were delivered."},"refundedAt":{"type":["string","null"],"format":"date-time","description":"When the order was refunded."},"creatorCode":{"anyOf":[{"type":"object","description":"The creator code of the order. The rates (buyer bonus, commission) are never exposed.","properties":{"code":{"type":"string","description":"The creator code, normalized (upper case)."},"creatorName":{"type":"string","description":"Name of the creator."}},"required":["code","creatorName"]},{"type":"null"}],"description":"Creator code applied to the order; `null` when the buyer used none."}},"required":["id","playerId","status","pack","amountCents","currency","country","vatRateBp","vatCents","netCents","createdAt","paidAt","fulfilledAt","refundedAt","creatorCode"]},"Redemption":{"type":"object","description":"What a gift code gave to the player.","properties":{"grants":{"type":"array","description":"Coins credited, one line per currency.","items":{"type":"object","description":"Free coins credited, with the balance afterwards.","properties":{"currency":{"type":"string","description":"Currency code."},"amount":{"type":"integer","description":"Units credited to the `bonus` bucket (gift codes never give paid coins)."},"balance":{"$ref":"#/components/schemas/Balance"}},"required":["currency","amount","balance"]}},"items":{"type":"array","description":"Items added to the inventory.","items":{"$ref":"#/components/schemas/PackItem"}},"skipped":{"type":"array","description":"Items the player could not receive (ownership limit reached).","items":{"$ref":"#/components/schemas/PackItem"}}},"required":["grants","items","skipped"]},"PlayerToken":{"type":"object","description":"A short-lived player token.","properties":{"token":{"type":"string","description":"Player token (`gc_pt_…`), sent as `Authorization: Bearer` on client routes."},"expiresAt":{"type":"string","format":"date-time","description":"Expiry (one hour after issue)."}},"required":["token","expiresAt"]},"Movement":{"type":"object","description":"A wallet change: the ledger entry and the resulting balance.","properties":{"entry":{"$ref":"#/components/schemas/LedgerEntry"},"balance":{"$ref":"#/components/schemas/Balance"}},"required":["entry","balance"]},"LedgerPage":{"type":"object","description":"A page of ledger entries.","properties":{"entries":{"type":"array","description":"Newest first.","items":{"$ref":"#/components/schemas/LedgerEntry"}},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` to get the next page; `null` on the last page."}},"required":["entries","nextCursor"]},"InventoryList":{"type":"object","description":"The inventory of a player.","properties":{"items":{"type":"array","description":"Owned items only (quantity above 0).","items":{"$ref":"#/components/schemas/InventoryItem"}}},"required":["items"]},"InventoryChange":{"type":"object","description":"An inventory change; the item is returned even when it reaches 0.","properties":{"item":{"$ref":"#/components/schemas/InventoryItem"}},"required":["item"]},"Purchase":{"type":"object","description":"An item bought with currency: the debit, the new balance and the item.","properties":{"entry":{"$ref":"#/components/schemas/LedgerEntry"},"balance":{"$ref":"#/components/schemas/Balance"},"item":{"$ref":"#/components/schemas/InventoryItem"}},"required":["entry","balance","item"]},"Checkout":{"type":"object","description":"A new order and its hosted payment page.","properties":{"order":{"$ref":"#/components/schemas/Order"},"checkoutUrl":{"type":"string","description":"Hosted payment page: send the player there.","format":"uri"}},"required":["order","checkoutUrl"]},"AnonymousPlayer":{"type":"object","description":"An anonymous (hosted) player and their credentials.","properties":{"player":{"$ref":"#/components/schemas/Player"},"playerSecret":{"type":"string","description":"Secret of the player, returned only here: store it on the device to get new tokens."},"token":{"type":"string","description":"First player token."},"expiresAt":{"type":"string","format":"date-time","description":"Expiry of `token`."}},"required":["player","playerSecret","token","expiresAt"]},"EmailCodeSent":{"type":"object","description":"A code was requested (neutral answer: it never tells whether the address is already linked).","properties":{"sent":{"type":"boolean","description":"Always `true`: the answer is the same whether or not the address is known."},"expiresInSeconds":{"type":"integer","description":"Life of the code (600)."}},"required":["sent","expiresInSeconds"]},"EmailVerification":{"type":"object","description":"The e-mail is verified: the address is linked to the token player, or the session switched to the player already linked.","properties":{"token":{"type":"string","description":"Player token for the player the session now belongs to."},"expiresAt":{"type":"string","format":"date-time","description":"Expiry of `token`."},"player":{"$ref":"#/components/schemas/Player"},"merged":{"type":"boolean","description":"`true` when the address was already linked to another player and the session switched to it (wallets are never merged)."},"playerSecret":{"type":["string","null"],"description":"New secret of the player you got back (replaces the old one, which cannot be given back); `null` when the player did not change."}},"required":["token","expiresAt","player","merged","playerSecret"]},"Me":{"type":"object","description":"The token player, their wallet and their inventory in one call.","properties":{"player":{"$ref":"#/components/schemas/Player"},"wallet":{"$ref":"#/components/schemas/Wallet"},"inventory":{"type":"array","description":"Owned items only.","items":{"$ref":"#/components/schemas/InventoryItem"}}},"required":["player","wallet","inventory"]},"ErrorCode":{"type":"string","enum":["VALIDATION_FAILED","IDEMPOTENCY_KEY_REQUIRED","UNAUTHENTICATED","PAYMENT_FAILED","FORBIDDEN","PLAYER_BLOCKED","ENV_NOT_ENABLED","NOT_FOUND","INSUFFICIENT_FUNDS","LIMIT_REACHED","IDEMPOTENCY_CONFLICT","CONFLICT","PACK_NOT_AVAILABLE","PACK_SOLD_OUT","RATE_LIMITED","INTERNAL"],"description":"- `VALIDATION_FAILED` (400): Body, query or path parameter invalid (`details.fieldErrors`).\n- `IDEMPOTENCY_KEY_REQUIRED` (400): `Idempotency-Key` missing or empty on a request that requires it.\n- `UNAUTHENTICATED` (401): Key missing, unknown or revoked; player token invalid or expired.\n- `PAYMENT_FAILED` (402): Payment refused, or no payment provider for this environment.\n- `FORBIDDEN` (403): Key kind not allowed here, anonymous players disabled, or item not purchasable from the client API.\n- `PLAYER_BLOCKED` (403): The player is blocked.\n- `ENV_NOT_ENABLED` (403): `live` key used by a studio that is not verified.\n- `NOT_FOUND` (404): Unknown player, currency, item, pack or order in this game and environment.\n- `INSUFFICIENT_FUNDS` (409): Wallet or item quantity too low (`details.required`, `details.available`).\n- `LIMIT_REACHED` (409): `maxOwned`, `maxPerPlayer` or the balance ceiling exceeded.\n- `IDEMPOTENCY_CONFLICT` (409): `Idempotency-Key` reused with a different request.\n- `CONFLICT` (409): Invalid state transition (e.g. refund of an unpaid order, consuming a durable item).\n- `PACK_NOT_AVAILABLE` (409): The pack is outside its sale window (`details.startsAt`, `details.endsAt`).\n- `PACK_SOLD_OUT` (409): The total stock of the pack is sold out.\n- `RATE_LIMITED` (429): Too many requests; wait `Retry-After` seconds.\n- `INTERNAL` (500): Unexpected server error."},"ErrorDetails":{"type":"object","description":"Developer-facing details. Not meant for parsing, except `required` / `available` on `INSUFFICIENT_FUNDS`.","properties":{"fieldErrors":{"type":"object","description":"Field name (or `_` for the whole body) to messages or codes (`COUNTRY_NOT_SELLABLE`, `CURSOR_INVALID`, `PLAYER_REF_INVALID`, `URL_INVALID`, `ORIGIN_MISMATCH`, `CODE_INVALID`…).","additionalProperties":{"type":"array","items":{"type":"string"}}},"required":{"type":"integer","description":"`INSUFFICIENT_FUNDS`: amount needed."},"available":{"type":"integer","description":"`INSUFFICIENT_FUNDS`: amount available."}},"additionalProperties":true},"Error":{"type":"object","description":"The error body of every non-2xx response.","properties":{"error":{"type":"object","properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"English message for developers."},"details":{"$ref":"#/components/schemas/ErrorDetails"}},"required":["code","message"]}},"required":["error"]},"UpdatePlayerRequest":{"type":"object","properties":{"displayName":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"description":"Name shown in your game (1 to 80 characters once trimmed)."},"email":{"anyOf":[{"type":"string","maxLength":254},{"type":"null"}],"description":"Contact e-mail (stored, never returned by the API)."},"country":{"anyOf":[{"type":"string","maxLength":8},{"type":"null"}],"description":"ISO 3166-1 alpha-2 country code, e.g. `BE`; used as the default country of checkouts."},"birthYear":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Year of birth, between 1900 and the current year (stored, never returned by the API)."}},"additionalProperties":false,"description":"Profile fields to set. A missing field is left unchanged; `null` clears it."},"WalletMovementRequest":{"type":"object","properties":{"currency":{"type":"string","minLength":1,"maxLength":24,"description":"Currency code from the catalog, e.g. `gems` (lowercase letters, digits and `_`)."},"amount":{"type":"integer","minimum":1,"maximum":1000000000000,"description":"Positive integer amount (at most 1,000,000,000,000)."},"reason":{"type":"string","maxLength":500,"description":"Free text recorded in the ledger (at most 500 characters)."},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Free key/value strings recorded in the ledger: at most 20 entries; keys of 1 to 64 characters (letters, digits, `_`, `.`, `-`); values of at most 500 characters."}},"required":["currency","amount"],"additionalProperties":false,"description":"Credit or debit one currency of the player's wallet."},"ClientSpendRequest":{"type":"object","properties":{"currency":{"type":"string","minLength":1,"maxLength":24,"description":"Currency code from the catalog."},"amount":{"type":"integer","minimum":1,"maximum":1000000000000,"description":"Positive integer amount (at most 1,000,000,000,000)."},"reason":{"type":"string","maxLength":500,"description":"Free text recorded in the ledger (at most 500 characters)."}},"required":["currency","amount"],"additionalProperties":false,"description":"Spend one currency of the token player's wallet."},"ItemQuantityRequest":{"type":"object","properties":{"sku":{"type":"string","minLength":1,"maxLength":48,"description":"Item sku."},"quantity":{"default":1,"type":"integer","minimum":1,"maximum":1000000000000,"description":"Number of units (defaults to 1)."}},"required":["sku"],"additionalProperties":false,"description":"An item and a number of units."},"ServerCheckoutRequest":{"type":"object","properties":{"packSku":{"type":"string","minLength":1,"maxLength":48,"description":"Pack sku."},"successUrl":{"type":"string","minLength":1,"maxLength":2048,"description":"Where to send the player after a successful payment (`https`; `http` only on localhost in `test`)."},"cancelUrl":{"type":"string","minLength":1,"maxLength":2048,"description":"Where to send the player when they cancel or the payment fails."},"country":{"type":"string","minLength":2,"maxLength":2,"description":"ISO alpha-2 country of the buyer, which decides the VAT (EU countries only). Defaults to the player's country, then `BE`."},"locale":{"type":"string","maxLength":35,"description":"Language of the hosted payment page, the receipt and the e-mails of this order: `fr`, `en` or `nl`. Any other value is ignored. Without it the payment page follows the player's browser language (`Accept-Language`), then French."},"creatorCode":{"type":"string","minLength":1,"maxLength":64,"description":"Creator code typed by the buyer (case, spaces and dashes are ignored): the buyer gets bonus coins on delivery and the creator earns a tracked commission. A refused code (unknown, disabled, not valid for this pack, or the creator's own player) is always `400 VALIDATION_FAILED` with `fieldErrors.creatorCode = [\"CODE_INVALID\"]`."}},"required":["packSku"],"additionalProperties":false,"description":"Order a currency pack for a player."},"ClientCheckoutRequest":{"type":"object","properties":{"packSku":{"type":"string","minLength":1,"maxLength":48,"description":"Pack sku."},"successUrl":{"type":"string","minLength":1,"maxLength":2048,"description":"Return URL after payment; must have the same origin as the request's `Origin` header (`ORIGIN_MISMATCH` otherwise)."},"cancelUrl":{"type":"string","minLength":1,"maxLength":2048,"description":"Return URL on cancel; same origin rule as `successUrl`."},"locale":{"type":"string","maxLength":35,"description":"Language of the hosted payment page, the receipt and the e-mails of this order: `fr`, `en` or `nl`. Any other value is ignored. Without it the payment page follows the player's browser language (`Accept-Language`), then French."},"creatorCode":{"type":"string","minLength":1,"maxLength":64,"description":"Creator code typed by the buyer (case, spaces and dashes are ignored): the buyer gets bonus coins on delivery and the creator earns a tracked commission. A refused code (unknown, disabled, not valid for this pack, or the creator's own player) is always `400 VALIDATION_FAILED` with `fieldErrors.creatorCode = [\"CODE_INVALID\"]`."}},"required":["packSku"],"additionalProperties":false,"description":"Order a currency pack for the token player."},"RedeemCodeRequest":{"type":"object","properties":{"code":{"type":"string","minLength":1,"maxLength":64,"description":"The gift code, as the player types it: case, spaces and dashes are ignored (at most 64 characters)."}},"required":["code"],"additionalProperties":false,"description":"A gift code typed by the player."},"RefundRequest":{"type":"object","properties":{"reason":{"type":"string","maxLength":500,"description":"Free text recorded with the refund (at most 500 characters)."}},"additionalProperties":false,"description":"Optional refund details. An empty body means `{}`."},"CreateAnonymousPlayerRequest":{"type":"object","properties":{"displayName":{"type":"string","maxLength":200,"description":"Name shown in your game (1 to 80 characters once trimmed)."}},"additionalProperties":false,"description":"Optional profile of the anonymous player. An empty body means `{}`."},"PlayerTokenRequest":{"type":"object","properties":{"playerId":{"type":"string","minLength":1,"maxLength":64,"description":"GameCoin player id returned by `POST /client/players`."},"playerSecret":{"type":"string","minLength":1,"maxLength":200,"description":"Secret returned once by `POST /client/players`; keep it on the device."}},"required":["playerId","playerSecret"],"additionalProperties":false,"description":"Credentials of an anonymous (hosted) player."},"RequestEmailCodeRequest":{"type":"object","properties":{"email":{"type":"string","minLength":1,"maxLength":318,"description":"A valid e-mail address (at most 254 characters); trimmed and lower-cased."},"locale":{"type":"string","minLength":2,"maxLength":35,"description":"Language of the e-mail: `fr`, `en` or `nl` (a tag such as `nl-BE` is accepted). Defaults to `Accept-Language`, then French."}},"required":["email"],"additionalProperties":false,"description":"Ask for a six-digit code to be sent to an e-mail address."},"VerifyEmailCodeRequest":{"type":"object","properties":{"email":{"type":"string","minLength":1,"maxLength":318,"description":"The address the code was sent to."},"code":{"type":"string","maxLength":32,"description":"The six digits; spaces and dashes are ignored."}},"required":["email","code"],"additionalProperties":false,"description":"The code received by e-mail."}},"parameters":{"Player":{"name":"player","in":"path","required":true,"description":"A GameCoin player id, or `ext:<your id>`. URL-encode the whole value exactly once: `encodeURIComponent(\"ext:\" + id)`. With a secret key, a `PUT` or `POST` on an unknown `ext:` player creates it; a `GET` answers `404`.","schema":{"type":"string","minLength":1,"maxLength":132},"examples":{"gamecoinId":{"summary":"GameCoin id","value":"665f1c2e8a3b4d5e6f708192"},"external":{"summary":"Your own id (before URL-encoding)","value":"ext:user-42"}}},"OrderId":{"name":"orderId","in":"path","required":true,"description":"Order id (24 hexadecimal characters).","schema":{"type":"string"},"example":"665f1c2e8a3b4d5e6f7081b4"},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"description":"1 to 100 printable ASCII characters, unique per operation you intend. Same key and same request: the original response comes back with `Idempotent-Replayed: true`. Same key and another request: `409 IDEMPOTENCY_CONFLICT`. Keys are kept 30 days.","schema":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[\\x20-\\x7E]{1,100}$"},"example":"grant-level-5-user-42"},"LedgerCurrency":{"name":"currency","in":"query","description":"Only the entries of this currency.","schema":{"type":"string","minLength":1,"maxLength":24}},"PageLimit":{"name":"limit","in":"query","description":"Page size, 1 to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},"PageCursor":{"name":"cursor","in":"query","description":"The `nextCursor` of the previous page. Opaque.","schema":{"type":"string","minLength":1,"maxLength":100}}},"headers":{"IdempotentReplayed":{"description":"Present with the value `true` when the response is the replay of a request already processed under the same `Idempotency-Key`.","schema":{"type":"string","enum":["true"]}},"RetryAfter":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","minimum":1}}},"responses":{"BadRequest":{"description":"Invalid request: `VALIDATION_FAILED` or `IDEMPOTENCY_KEY_REQUIRED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"VALIDATION_FAILED":{"summary":"VALIDATION_FAILED","value":{"error":{"code":"VALIDATION_FAILED","message":"Invalid request body","details":{"fieldErrors":{"amount":["Too small: expected number to be >=1"]}}}}},"IDEMPOTENCY_KEY_REQUIRED":{"summary":"IDEMPOTENCY_KEY_REQUIRED","value":{"error":{"code":"IDEMPOTENCY_KEY_REQUIRED","message":"The Idempotency-Key header is required on this request"}}}}}}},"Unauthenticated":{"description":"Missing, unknown or revoked key; invalid or expired player token (not distinguishable).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"UNAUTHENTICATED":{"summary":"UNAUTHENTICATED","value":{"error":{"code":"UNAUTHENTICATED","message":"Invalid API key"}}}}}}},"PaymentFailed":{"description":"Payment refused, or no payment provider for this environment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"PAYMENT_FAILED":{"summary":"PAYMENT_FAILED","value":{"error":{"code":"PAYMENT_FAILED","message":"No payment provider is available in the live environment"}}}}}}},"Forbidden":{"description":"Not allowed: `FORBIDDEN`, `PLAYER_BLOCKED` or `ENV_NOT_ENABLED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"FORBIDDEN":{"summary":"FORBIDDEN","value":{"error":{"code":"FORBIDDEN","message":"This endpoint requires a secret API key"}}},"PLAYER_BLOCKED":{"summary":"PLAYER_BLOCKED","value":{"error":{"code":"PLAYER_BLOCKED","message":"Player is blocked"}}},"ENV_NOT_ENABLED":{"summary":"ENV_NOT_ENABLED","value":{"error":{"code":"ENV_NOT_ENABLED","message":"The live environment is reserved for verified studios"}}}}}}},"NotFound":{"description":"Unknown player, currency, item, pack or order in this game and environment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"NOT_FOUND":{"summary":"NOT_FOUND","value":{"error":{"code":"NOT_FOUND","message":"Player not found"}}}}}}},"Conflict":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"INSUFFICIENT_FUNDS":{"summary":"INSUFFICIENT_FUNDS","value":{"error":{"code":"INSUFFICIENT_FUNDS","message":"Insufficient funds","details":{"required":50,"available":20}}}},"LIMIT_REACHED":{"summary":"LIMIT_REACHED","value":{"error":{"code":"LIMIT_REACHED","message":"Item ownership limit exceeded","details":{"maxOwned":1,"owned":1}}}},"PACK_NOT_AVAILABLE":{"summary":"PACK_NOT_AVAILABLE","value":{"error":{"code":"PACK_NOT_AVAILABLE","message":"Pack is not on sale","details":{"startsAt":"2026-11-01T00:00:00.000Z","endsAt":null}}}},"PACK_SOLD_OUT":{"summary":"PACK_SOLD_OUT","value":{"error":{"code":"PACK_SOLD_OUT","message":"Pack is sold out","details":{"packSku":"founder-pack"}}}},"IDEMPOTENCY_CONFLICT":{"summary":"IDEMPOTENCY_CONFLICT","value":{"error":{"code":"IDEMPOTENCY_CONFLICT","message":"The Idempotency-Key was already used with a different request"}}},"CONFLICT":{"summary":"CONFLICT","value":{"error":{"code":"CONFLICT","message":"The order can no longer be changed","details":{"status":"pending"}}}}}}}},"RateLimited":{"description":"Too many requests.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"RATE_LIMITED":{"summary":"RATE_LIMITED","value":{"error":{"code":"RATE_LIMITED","message":"Too many requests"}}}}}}},"InternalError":{"description":"Unexpected error; retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"INTERNAL":{"summary":"INTERNAL","value":{"error":{"code":"INTERNAL","message":"Internal error"}}}}}}}},"securitySchemes":{"secretKey":{"type":"http","scheme":"bearer","bearerFormat":"gc_sk_test_… | gc_sk_live_…","description":"Secret API key of a game and environment (`test` or `live`): `Authorization: Bearer gc_sk_…`. Server-side only; never ship it in a client."},"publishableKey":{"type":"apiKey","in":"header","name":"X-GameCoin-Key","description":"Publishable key (`gc_pk_…`) of a game and environment. Safe in a browser or a game client."},"playerToken":{"type":"http","scheme":"bearer","bearerFormat":"gc_pt_…","description":"Player token (one hour), sent as `Authorization: Bearer gc_pt_…` together with the publishable key. Issued by `POST /players/{player}/tokens` (server) or the client player routes."}}}}