Skip to content
Skip the menu

API reference

Checkouts API

Sell a currency pack for real money by creating an order and sending the player to the hosted payment page, with the server API.

View as Markdown

On this page

A checkout is how a player pays real money for a pack: a bundle of currency and items that you define in the dashboard (« Packs », in French for now). You create an order, GameCoin returns the address of a hosted payment page, and you send the player there. When the payment succeeds, the coins and items are delivered to the player. You never handle card details. Payments are simulated in the test environment: the payment page offers "Pay (simulated)" and "Decline"; real payments are not available yet. The examples on this page continue from the setup.

Order a currency pack

Creates an order for a pack and returns the hosted payment page. Use it when a player taps "Buy" on a pack in your shop. The call returns at once with a pending order: the coins are delivered later, when the player pays.

POST/players/{player}/checkouts

Authentication: secret key. Idempotency: required (Idempotency-Key header).

Path parameters

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

Body

FieldTypeRequiredRules
packSkustringyesThe sku of an active pack: 1 to 48 characters.
successUrlstringnoWhere to send the player after a successful payment: at most 2048 characters, https (http only on localhost, in the test environment).
cancelUrlstringnoWhere to send the player when the payment fails or is refused. Same rules as successUrl.
countrystringnoThe buyer's country, two letters (ISO 3166-1 alpha-2), which decides the VAT. Only countries of the European Union are sold to. Defaults to the player's country if it is sold to, otherwise BE.
localestringnoLanguage of the hosted payment page, the receipt and the e-mails of this order: fr, en or nl. Any other value is ignored (no error). Without it, the payment page follows the player's browser language (Accept-Language), then French.
creatorCodestringnoThe creator code the buyer typed: 1 to 64 characters, case, spaces and dashes ignored. The buyer gets a bonus of free coins and the creator earns a tracked commission, both when the order is delivered.
LangageLanguage
curl -X POST "https://gamecoin.apilow.com/api/v1/players/ext%3Auser-42/checkouts" \
  -H "Authorization: Bearer $GAMECOIN_SECRET_KEY" \
  -H "Idempotency-Key: checkout-gems-500-user-42" \
  -H "Content-Type: application/json" \
  -d '{"packSku": "gems-500", "successUrl": "https://example.com/shop/thanks", "cancelUrl": "https://example.com/shop", "country": "BE"}'

Response 201 Created for a new order, 200 OK for a replay.

FieldTypeDescription
orderOrderThe new order, pending. The pack, the amount and the VAT are frozen at this moment.
checkoutUrlstringThe hosted payment page of the order, /pay/{orderId}?t=…. Send the player there.
JSON
{
  "order": {
    "id": "665f1c2e8a3b4d5e6f7081b4",
    "playerId": "665f1c2e8a3b4d5e6f708192",
    "status": "pending",
    "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:39.356Z",
    "paidAt": null,
    "fulfilledAt": null,
    "refundedAt": null,
    "creatorCode": null
  },
  "checkoutUrl": "https://gamecoin.apilow.com/pay/665f1c2e8a3b4d5e6f7081b4?t=…"
}

Send the same request again with the same key and you get the same order in its current state and the same checkoutUrl, with 200 and Idempotent-Replayed: true, and no second order. After the player has paid, the replay shows it:

Bash
curl -i -X POST "https://gamecoin.apilow.com/api/v1/players/ext%3Auser-42/checkouts" \
  -H "Authorization: Bearer $GAMECOIN_SECRET_KEY" \
  -H "Idempotency-Key: checkout-gems-500-user-42" \
  -H "Content-Type: application/json" \
  -d '{"packSku": "gems-500", "successUrl": "https://example.com/shop/thanks", "cancelUrl": "https://example.com/shop", "country": "BE"}'
HTTP
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Idempotent-Replayed: true

{
  "order": {
    "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:39.356Z",
    "paidAt": "2026-10-05T22:14:39.826Z",
    "fulfilledAt": "2026-10-05T22:14:39.826Z",
    "refundedAt": null,
    "creatorCode": null
  },
  "checkoutUrl": "https://gamecoin.apilow.com/pay/665f1c2e8a3b4d5e6f7081b4?t=…"
}

Errors

StatusCodeWhenWhat to do
400IDEMPOTENCY_KEY_REQUIREDThe Idempotency-Key header is missing or emptySend one: see Idempotency.
400VALIDATION_FAILEDdetails.fieldErrors.creatorCode is ["CODE_INVALID"]: the creator code is unknown, disabled, not valid for this pack, or the creator's own player (always the same answer, and no order is created)Tell the buyer the code is not valid; let them retry or buy without it.
400VALIDATION_FAILEDAn unknown field; packSku empty or longer than 48 characters; country not two letters, or a country that is not sold to (COUNTRY_NOT_SELLABLE); a return URL that is not a valid URL (URL_INVALID) or not https (URL_SCHEME_NOT_ALLOWED); a key that is not 1 to 100 printable ASCII charactersRead details.fieldErrors.
402PAYMENT_FAILEDNo payment provider is available for the environmentUse a test key: payments are simulated there.
403PLAYER_BLOCKEDThe player is blockedNo order can be created for this player until the player is unblocked.
404NOT_FOUNDThe pack is not in the catalog (or is archived), or a GameCoin id does not existCheck the sku against Get the catalog.
409LIMIT_REACHEDThe player already bought the pack as many times as its maxPerPlayer allows: details.maxPerPlayer and details.boughtHide the pack for this player.
409PACK_NOT_AVAILABLEThe pack is outside its sale window: details.startsAt and details.endsAt (ISO 8601 or null)Show « coming soon » or « ended ». See Founder packs.
409PACK_SOLD_OUTThe total stock of the pack is used upShow « sold out ». The stock can come back if a pending order expires.
409IDEMPOTENCY_CONFLICTThe key was already used with a different requestUse a new key for a new order.
JSON
{
  "error": {
    "code": "LIMIT_REACHED",
    "message": "Pack purchase limit reached",
    "details": { "maxPerPlayer": 1, "bought": 1 }
  }
}

Notes

  • The call creates an order, not a payment. The coins and items are delivered when the order becomes fulfilled, a moment after the player pays. Do not give anything yourself when the call returns.
  • After payment the player is redirected to successUrl with ?order=<id>&status=fulfilled added. When they decline, or the order fails or expires, they are redirected to cancelUrl with ?order=<id>&status=failed (or expired). Without these URLs the player lands on a built-in confirmation page, which also notifies the window that opened it with postMessage({ type: "gamecoin:order", orderId, status }).
  • Never trust the query string: when the player comes back, read the order with Get an order and check its status before you tell them it worked.
  • If an item of the pack cannot be delivered, because the player already owns the most they can of a durable item, the pack is still delivered: the currency and the other items arrive. See Selling packs.
  • The buyer confirms their country on the payment page, and the VAT is recomputed if they change it. That is why country on the order can differ from the one you sent.
  • A pending order that is not paid within one hour becomes expired. The player can open checkoutUrl as long as the order is pending.
  • Limited packs. A new order holds one unit of the stock of a pack that has one, from the moment it is created. The unit goes back when the order fails, expires or is refunded, and becomes a sale when the order is delivered. A second request for the same pack by the same player returns the pending order and holds nothing more. The sale window is checked when the order is created: a player who started in time can still pay after the window closes, within the hour.
  • maxPerPlayer counts the orders that are paid or fulfilled, and it is checked when the order is created. An order that was refunded, never paid or declined does not count. Two orders created before the first one is paid are both accepted, and both can then be paid: when a pack is limited, create the next order only after the previous one is no longer pending.
  • successUrl and cancelUrl are free on this server route. The browser route only accepts addresses on the origin of the page: see Order a currency pack in the client API.
  • The checkoutUrl is a link for one player: do not log it, and do not share it.