Skip to content
Skip the menu

Concepts

Errors

The shape of every error the API returns, the full table of codes, and how to handle the ones your game will meet.

View as Markdown

On this page

The shape

Every error has the same JSON body and a matching HTTP status:

JSON
{
  "error": {
    "code": "INSUFFICIENT_FUNDS",
    "message": "Insufficient funds",
    "details": { "required": 50, "available": 20 }
  }
}
FieldMeaning
codeA stable code in capitals. Branch on this, never on message.
messageA sentence in English for developers and logs. It can change; do not show it to players.
detailsOptional. Extra facts, listed per code below.

Show your players your own text, chosen from the code.

All the codes

HTTPCodeWhen
400VALIDATION_FAILEDThe body, the query or the player reference is invalid. See details.fieldErrors.
400IDEMPOTENCY_KEY_REQUIREDA write that needs an Idempotency-Key has none
401UNAUTHENTICATEDThe key is missing, unknown or revoked, or the player token is invalid or expired
402PAYMENT_FAILEDThe payment was refused, or there is no payment provider for this environment
403FORBIDDENThis kind of key cannot do this; anonymous players are turned off; the item cannot be bought from the game
403PLAYER_BLOCKEDThe player is blocked
403ENV_NOT_ENABLEDA live key was used by a studio that is not verified
404NOT_FOUNDUnknown player, currency, item, pack or order, in this game and environment
409INSUFFICIENT_FUNDSThe wallet, or the quantity of an item, is too low
409LIMIT_REACHEDA limit is reached: an item's maxOwned, a pack's maxPerPlayer, or the balance ceiling
409PACK_NOT_AVAILABLEA pack is outside its sale window
409PACK_SOLD_OUTThe total stock of a pack is used up
409IDEMPOTENCY_CONFLICTAn idempotency key was reused with another request
409CONFLICTThe action does not fit the current state, for example refunding an unpaid order or consuming a durable item
429RATE_LIMITEDToo many requests. See Retry-After.
500INTERNALAn unexpected error on our side

The SDKs add three codes for problems that have no HTTP answer:

CodeHTTPMeaning
NETWORK_ERROR0No answer arrived (connection lost, DNS, firewall)
TIMEOUT0The SDK gave up waiting
INVALID_RESPONSEas receivedThe answer was not the JSON GameCoin sends, for example an HTML error page from a proxy

Details by code

INSUFFICIENT_FUNDS

details.required and details.available give the amount you asked for and what the player has. The same code covers an item: consuming more potions than the player owns.

JSON
{ "error": { "code": "INSUFFICIENT_FUNDS", "message": "Not enough items", "details": { "required": 5, "available": 1 } } }

LIMIT_REACHED

For an item, details has maxOwned and owned. For a pack, it has maxPerPlayer and bought.

JSON
{ "error": { "code": "LIMIT_REACHED", "message": "Item ownership limit exceeded", "details": { "maxOwned": 1, "owned": 1 } } }

PACK_NOT_AVAILABLE

The pack is not on sale now: its sale has not started, or it has ended. details.startsAt and details.endsAt are ISO 8601 dates in UTC, or null when that side is open.

JSON
{ "error": { "code": "PACK_NOT_AVAILABLE", "message": "The pack is not on sale", "details": { "startsAt": "2026-11-01T09:00:00.000Z", "endsAt": null } } }

PACK_SOLD_OUT

The pack has a total stock and all its units are sold or held by pending orders. details.packSku names the pack. Units held by a pending order that is never paid come back after one hour.

VALIDATION_FAILED

details.fieldErrors maps each wrong field to a list of messages or codes:

JSON
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Invalid request body",
    "details": { "fieldErrors": { "paid": ["Unrecognized field"] } }
  }
}

Request bodies are strict: a field the route does not know is an error, not something that is ignored. details.fieldErrors is for developers; do not parse its messages. A few values are stable codes:

Field errorMeaning
PLAYER_REF_INVALIDThe {player} in the path is not a GameCoin id or an ext: reference, often because it was encoded twice
COUNTRY_NOT_SELLABLEThe country of a checkout is not a country where packs are sold
URL_INVALID, URL_SCHEME_NOT_ALLOWEDA successUrl or cancelUrl is not acceptable
ORIGIN_MISMATCHA browser checkout's return URL does not have the origin of the page
CURSOR_INVALIDThe cursor of a list is not one GameCoin gave
CODE_INVALIDA gift code (code) or a creator code (creatorCode) was refused. One answer for every cause (unknown, expired, used up, disabled, already used, not valid for this pack…), so codes cannot be guessed: see Codes API

RATE_LIMITED

The Retry-After header holds the number of seconds to wait. See Rate limits.

IDEMPOTENCY_CONFLICT and IDEMPOTENCY_KEY_REQUIRED

They carry no details. A request that failed never uses up its idempotency key, so you can send it again with the same key. See Idempotency.

Handle errors

LangageLanguage
import { ErrorCode, GameCoinError } from "@apilow/gamecoin";

try {
  await gamecoin.wallet.spend(ext("user-42"), { currency: "gems", amount: 50 }, { idempotencyKey: "revive-user-42-7" });
} catch (error) {
  if (!(error instanceof GameCoinError)) throw error;
  if (error.code === ErrorCode.INSUFFICIENT_FUNDS) {
    const { required, available } = error.details; // 50, 20
    console.log(`short by ${required - available}: offer a pack`);
  } else if (error.code === ErrorCode.RATE_LIMITED) {
    console.log(`wait ${error.retryAfter} seconds`);
  } else {
    throw error; // error.code, error.status, error.message, error.details
  }
}

What to do for each code in a game:

CodeIn your game
INSUFFICIENT_FUNDSOffer a pack, or a cheaper choice. This is a normal event, not a failure.
LIMIT_REACHEDTell the player they already have it, or have bought it the most times
PACK_NOT_AVAILABLEShow « coming soon » or « ended » (read the dates in details)
PACK_SOLD_OUTShow « sold out »
UNAUTHENTICATEDIn a browser, the SDK renews the token once by itself. If it still fails, check the key. On your server, check the secret key and its environment.
PLAYER_BLOCKEDShow your own "account suspended" message
VALIDATION_FAILEDA bug in your request. Log details and fix the code.
RATE_LIMITEDWait Retry-After seconds. The SDKs do it for you for short waits.
NETWORK_ERRORThe write may or may not have happened. Retry with the same idempotency key, or read the balance first.
INTERNALRetry after a pause. If it lasts, report it.

Where next