Skip to content
Skip the menu

API reference

API reference

The GameCoin API v1 in one place, with the rules every call shares and an example in six languages for each operation.

View as Markdown

On this page

GameCoin keeps the virtual currency, the inventory and the sales of your game behind one HTTP API. This page gives the rules that every operation shares; the pages that follow describe each resource, one section per operation. For each operation you will find what to send, what you get back, what can fail, and an example you can copy in curl, Node.js, Python, PHP, Go and C#.

Base URL

Every route lives under /api/v1. For this service the base URL is:

https://gamecoin.apilow.com/api/v1

Requests and responses are JSON (Content-Type: application/json). Paths in this reference are relative to the base URL: GET /catalog means GET https://gamecoin.apilow.com/api/v1/catalog.

Two APIs

Server APIClient API
Called fromYour game backendA browser or a game client
RoutesEverything except /client/**/client/**
CredentialSecret key, Authorization: Bearer gc_sk_test_…Publishable key, X-GameCoin-Key: gc_pk_test_…, plus a player token on player routes
Can credit a playerYes: grants, pack deliveries, refundsNever
CORSNone: never call it from a browserRestricted to the game's allowed origins (open in test while the list is empty)
ReferenceCatalog, Players, Wallet, Ledger, Inventory, Purchases, Checkouts, Orders, CodesClient API

A browser can read, spend, buy an item with a currency, consume and pay for a pack. Crediting needs your secret key, on your server (the only exception: a gift code that you issued adds free coins, once per player). Security model explains why this split exists.

Authentication

Server API. Send your secret key as a bearer token:

HTTP
Authorization: Bearer gc_sk_test_…

Client API. Send your publishable key in X-GameCoin-Key. Routes that act on a player (/client/me/**) also need a player token, which lasts one hour:

HTTP
X-GameCoin-Key: gc_pk_test_…
Authorization: Bearer gc_pt_…

You get a player token from your backend with Issue a player token, or for an anonymous player from the Client API.

The key decides the game and the environment (test or live). Nothing in a URL or a body can change that. The live environment is reserved for verified studios and answers 403 ENV_NOT_ENABLED to the others. See Keys and environments.

Warning

A secret key must never leave your server. Keep it in an environment variable or a secret manager, and never put it in a game client, a web page or a repository. The examples read it from GAMECOIN_SECRET_KEY.

Wrong credentials are refused in a way that does not leak anything. A missing, malformed, unknown or revoked key is 401 UNAUTHENTICATED. A key of the wrong kind for the route (a publishable key sent as the bearer of a server route, a secret key sent in X-GameCoin-Key to a client route) is 403 FORBIDDEN. An invalid or expired player token is 401 UNAUTHENTICATED, and the two cases look the same.

Referencing a player

The {player} segment of a path is either:

  • a GameCoin player id: 24 hexadecimal characters, such as 665f1c2e8a3b4d5e6f708192;
  • or your own id, prefixed with ext:, such as ext:user-42.

Your own id is opaque and case-sensitive: 1 to 128 characters, with no leading or trailing whitespace. Anything else is allowed (spaces, slashes, accents).

The whole reference is URL-encoded exactly once, with encodeURIComponent("ext:" + id) or its equivalent. The SDKs do it for you.

Your idIn the path
user-42ext%3Auser-42
Zoë Müller/1%ext%3AZo%C3%AB%20M%C3%BCller%2F1%25

Encoding twice, or sending a reference that is neither a 24-character hexadecimal id nor an ext: reference, is 400 VALIDATION_FAILED with details.fieldErrors.player set to PLAYER_REF_INVALID.

With a secret key, the first write on an unknown ext: player creates it: the PUT /players/{player} and every POST /players/{player}/… (tokens, grants, spends, purchases, checkouts) need no registration step. The player is created even when the write then fails, for instance a spend that answers INSUFFICIENT_FUNDS. A read (GET) on an unknown player answers 404 NOT_FOUND. A GameCoin id that does not exist is always 404, because only ext: players are created on the fly.

Requests and responses

  • Strict bodies. An unknown field is a 400 VALIDATION_FAILED, with the field named in details.fieldErrors. This is deliberate: { "paid": 100 } on a grant fails instead of being ignored. Malformed JSON, and a body that is not an object, fail the same way.
  • Size. A body is at most 64 KiB (65,536 bytes), otherwise 400 VALIDATION_FAILED.
  • Empty body. Where every field is optional (PUT /players/{player}, refund, POST /client/players), an empty body means {}.
  • Dates are ISO 8601 strings in UTC, with milliseconds: 2026-10-05T21:37:21.993Z.
  • Amounts are integers, with no decimals: virtual currencies in their smallest unit, euro prices in cents. A movement is between 1 and 1,000,000,000,000 (10¹²), and a balance can never exceed 1,000,000,000,000,000 (10¹⁵), otherwise 409 LIMIT_REACHED.
  • Caching. Every response carries Cache-Control: no-store, except the OpenAPI document.
  • Query strings. Unknown query parameters are ignored.

Idempotency

Every POST that changes a wallet or an inventory, or creates a checkout, requires an Idempotency-Key header: 1 to 100 printable ASCII characters. It makes a retry safe: if your request times out, send it again with the same key and the change happens once.

HTTP
Idempotency-Key: grant-level-5-user-42
You sendYou get
A key never seenThe request runs; the response is stored under the key
The same key and the same requestThe original response, with the header Idempotent-Replayed: true. Nothing runs twice.
The same key and a different request409 IDEMPOTENCY_CONFLICT
No key, or an empty one400 IDEMPOTENCY_KEY_REQUIRED
A key that is too long or has characters outside printable ASCII400 VALIDATION_FAILED
  • The "same request" means the same route, the same player and the same body. The key is shared by every operation of the game and the environment: reusing it on another route or another player is a conflict.
  • Keys are kept 30 days.
  • Only successful responses are stored. A request that failed (INSUFFICIENT_FUNDS, VALIDATION_FAILED…) did nothing and can be retried with the same key.
  • A replayed checkout returns the order in its current state, not the one it had at creation (see Order a currency pack).
  • POST /orders/{orderId}/refund has no idempotency key: check the order with GET /orders/{orderId} before retrying it.

The operations that take a key are marked Idempotency: required in this reference. The SDKs generate a key for you, once per call, and reuse it for every automatic retry. See Idempotency.

Pagination

Lists use a cursor. Send ?limit= (1 to 100, default 20) and ?cursor=. The response carries nextCursor: pass it as cursor to get the next page, until it is null. A cursor is opaque: do not build or parse one. An invalid cursor is 400 VALIDATION_FAILED with CURSOR_INVALID in details.fieldErrors.cursor. Only the ledger is paginated today.

Status codes

StatusMeaning
200Success. This includes the replay of a request that was already processed.
201A new anonymous player (POST /client/players), or a new checkout (not a replay).
204A CORS preflight (OPTIONS) on a client route.
400Invalid request: VALIDATION_FAILED or IDEMPOTENCY_KEY_REQUIRED.
401UNAUTHENTICATED.
402PAYMENT_FAILED.
403FORBIDDEN, PLAYER_BLOCKED or ENV_NOT_ENABLED.
404NOT_FOUND.
409INSUFFICIENT_FUNDS, LIMIT_REACHED, IDEMPOTENCY_CONFLICT or CONFLICT.
429RATE_LIMITED, with Retry-After.
500INTERNAL.

Requests are processed in this order: authentication, rate limit, idempotency key, body validation. So a request with a bad key never reaches validation, and a missing Idempotency-Key is reported before a malformed body.

Errors common to every operation

Every error has the same shape:

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

code is stable and is what your code should test. message is English text for developers, not for your players. details is optional and only guaranteed for required and available on INSUFFICIENT_FUNDS; details.fieldErrors on VALIDATION_FAILED maps a field to messages or codes, and is meant for you to read, not to parse.

Any operation can fail with these errors, so the tables of the next pages do not repeat them:

StatusCodeWhenWhat to do
401UNAUTHENTICATEDMissing, malformed, unknown or revoked key; invalid or expired player tokenCheck the key and its environment. For a player token, issue a new one.
403FORBIDDENThe key is of the wrong kind for the routeUse the secret key on the server API and the publishable key on the client API.
403ENV_NOT_ENABLEDA live key used by a studio that is not verifiedUse a test key, or get the studio verified.
429RATE_LIMITEDToo many requestsWait Retry-After seconds, then retry.
500INTERNALUnexpected error on our sideRetry later; a request with an idempotency key is safe to repeat.

The complete list of codes is in Errors.

Rate limits

ScopeLimit
Server API, per secret key600 requests per minute
Client API with a player token, per player120 requests per minute
Client API without a token, per IP address120 requests per minute
Anonymous player creation, per IP address30 per hour

A 429 answer carries a Retry-After header: the number of seconds to wait. The SDKs wait and retry for you. See Rate limits.

OpenAPI document

The same contract is published as an OpenAPI 3.1 document, with schemas, examples and the errors of every operation: 23 paths and 24 operations. Use it to generate a client, to import the API into Postman or Insomnia, or to feed a tool.

GET https://gamecoin.apilow.com/api/v1/openapi.json

It is public: no key, no rate limit, Access-Control-Allow-Origin: *, and Cache-Control: public, max-age=300. Its servers entry is this service. Open it here.

Setup for the examples

The examples of the next pages share one setup: a client built with your secret key, and a player whose own id is user-42. Copy it once; each example then continues from it. The SDKs are the server SDKs of Node.js, Python, PHP, Go and C#: their pages tell you how to install them.

The curl examples read your key from GAMECOIN_SECRET_KEY (the client API ones also use GAMECOIN_PUBLISHABLE_KEY and GAMECOIN_PLAYER_TOKEN). Use a test key: the test environment is where payments are simulated.

LangageLanguage
export GAMECOIN_SECRET_KEY="gc_sk_test_…"   # a secret test key: « Clés d'API » in the dashboard
# The player "ext:user-42" is written ext%3Auser-42 in a path (URL-encoded once)

Note

The SDKs are pre-release, and the package names above are the planned ones. The examples run as written against the test environment; on your own project, use the gc_sk_test_… key of your game (the dashboard is in French for now: « Clés d'API »), and the currencies, items and packs of your own catalog in place of the gems, potion and gems-500 of the examples.

The examples use a game with two currencies, gems (sold for real money) and gold (free); two items, potion (consumable, 20 gems) and fire-sword (durable, 300 gems, at most one per player); and two packs, starter (once per player) and gems-500.

All operations

Server API, with a secret key:

OperationIdempotency
GET /catalog reads the catalog
PUT /players/{player} creates or updates a player
GET /players/{player} reads a player
POST /players/{player}/tokens issues a player token
GET /players/{player}/wallet reads a wallet
POST /players/{player}/wallet/grant grants currencyrequired
POST /players/{player}/wallet/spend spends currencyrequired
GET /players/{player}/ledger lists ledger entries
GET /players/{player}/inventory lists the inventory
POST /players/{player}/inventory/grant grants an itemrequired
POST /players/{player}/inventory/consume consumes an itemrequired
POST /players/{player}/purchases buys an item with currencyrequired
POST /players/{player}/checkouts orders a currency packrequired
POST /players/{player}/codes/redeem redeems a gift coderequired
GET /orders/{orderId} reads an order
POST /orders/{orderId}/refund refunds an ordernone: see the page

Client API, with a publishable key (and a player token where marked):

OperationPlayer tokenIdempotency
GET /client/catalog reads the catalog
POST /client/players creates an anonymous player
POST /client/players/token renews an anonymous player's token
GET /client/me reads the player, wallet and inventoryyes
POST /client/me/wallet/spend spends currencyyesrequired
POST /client/me/purchases buys an item with currencyyesrequired
POST /client/me/inventory/consume consumes an itemyesrequired
POST /client/me/checkouts orders a currency packyesrequired
POST /client/me/codes/redeem redeems a gift codeyesrequired
GET /client/me/orders/{orderId} reads an orderyes

The objects these operations return are described in Objects.