Error codes

Remote calls fail with an InstfunError; branch on error.code and never parse the message. Local mode typically only produces INVALID_*, INVALID_SCORE and INSUFFICIENT_FUNDS.

InstfunError shape

class InstfunError extends Error {
  readonly code: InstfunErrorCode;
  readonly details?: Record<string, unknown>; // shape varies by code and is not guaranteed serializable
}

The enum has 17 members: INVALID_CONFIG, INVALID_AMOUNT, INVALID_SCORE, INVALID_INPUT, INSUFFICIENT_FUNDS, REWARD_NOT_ALLOWED, RATE_LIMITED, UNAUTHORIZED, INVALID_CODE, EMAIL_ALREADY_BOUND, EMAIL_SEND_UNAVAILABLE, CONFLICT, NOT_FOUND, NETWORK, SERVER_ERROR, PAYLOAD_TOO_LARGE, UNKNOWN.

Error code table

codeTriggerSuggested handling
INSUFFICIENT_FUNDSwallet.spend() balance too low (details carry currency / balance / requested)Do not retry; refresh the balance and tell the player
INVALID_SCORE / INVALID_AMOUNT / INVALID_INPUTInvalid arguments (score, amount, payload)Fix the call; this is a code issue
INVALID_CONFIGInvalid initialization config (for example a bad gameId)Fix the Instfun.init config
NETWORKOffline or request failureDo not block gameplay; saves/records/leaderboard are already in the outbox, call sdk.sync() once connectivity returns; ask players to retry wallet and ad rewards later
UNAUTHORIZEDExpired token (401)Call Instfun.init again or reload; the SDK re-logs into the same account with the local deviceSeed
INVALID_CODEWrong or expired email verification code (400)Handled inside the email binding dialog: re-enter or resend
EMAIL_ALREADY_BOUNDEmail already bound to another account (409)Use another email; the dialog shows an inline message
EMAIL_SEND_UNAVAILABLEEmail service unavailable (503)Try again later; the dialog shows an inline message
RATE_LIMITEDDaily quota or frequency limit hit (429)Saves go to the outbox and retry on the next sdk.sync(); do not auto-retry wallet or ads, show a "daily limit reached" message
PAYLOAD_TOO_LARGEBody or local validation over the limit (413)Do not retry; trim or compress the data and resubmit
CONFLICTAd receipt invalid, expired or already redeemed (409)Treat as unrecoverable; ask the player to watch again, never auto-grant
REWARD_NOT_ALLOWEDCredit exceeds the server reward policy (403)Do not retry; check reason / amount against the policy
NOT_FOUNDResource missing (404)Check identifiers such as boardId; usually a call-site error
SERVER_ERRORServer failure (5xx or a missing envelope)Retry later; save operations enter the outbox automatically
UNKNOWNUnclassified failureLog it and tell the player to try again later

Example

The CDN bundle only exposes the global Instfun, so check error.code directly:

try {
  await sdk.wallet.spend({ amount: 100, reason: "revive" });
} catch (error) {
  switch (error?.code) {
    case "INSUFFICIENT_FUNDS":
      // Tell the player; do not retry
      break;
    case "NETWORK":
      // Ask them to try later; saves/leaderboard reconcile on sdk.sync()
      break;
    case "RATE_LIMITED":
      // Show "daily limit reached"; do not auto-retry
      break;
    default:
      // Fallback: log error.code and details
      break;
  }
}

With a source dependency you can import InstfunError from the SDK package and narrow with error instanceof InstfunError. Whichever integration you use, branch on error.code.

Automatic re-login on 401

  • In remote mode the SDK exchanges a local deviceSeed for a server guest account. When the token expires (UNAUTHORIZED), the SDK clears it, logs in again with the same deviceSeed (the same account) and retries the original call, such as the email code send/verify endpoints.
  • If the retry still fails, the original UNAUTHORIZED is thrown; the email binding dialog shows an inline "session expired, refresh the page" message.
  • When game code sees an unhandled UNAUTHORIZED, call Instfun.init again or reload the page. No local data needs to be cleared.

429, rate limits and offline advice

  • There are two kinds of 429. Security-layer rate limits and daily quotas (saves/records/leaderboard) return Retry-After, and details contains retryAfterSeconds / limit / window. Wallet daily reward caps and ad placement daily caps are legacy 429s carrying only dailyCap / maxPerDay.
  • Saves, records and leaderboard submissions are retryable in the outbox and deferred to the next sdk.sync(). Wallet and ad rewards surface the error to the game; do not retry automatically.
  • On NETWORK, do not block gameplay: local-first data (saves/records/leaderboard) is already in the outbox; call sdk.sync() when connectivity returns or the page comes back to the foreground.
  • Responses without a JSON envelope also fall back by HTTP status: 401 → UNAUTHORIZED, 404 → NOT_FOUND, 409 → CONFLICT, 413 → PAYLOAD_TOO_LARGE, 429 → RATE_LIMITED, and all other 5xx → SERVER_ERROR.

The submit page (/platform/submit/) uses the website's own upload error codes (EMAIL_REQUIRED, INVALID_PACKAGE, GAME_ID_CONFLICT, PAYLOAD_TOO_LARGE, RATE_LIMITED, STORAGE_UNAVAILABLE), which do not pass through the SDK. See Publishing.