GiftingStack
Sign in Get started
Docs menu

Docs

Errors

Every error has the same shape. Branch on type, show message to developers, never to end users.

Checked against the API and the live OpenAPI on 2026-10-21.

The shape

HTTP/1.1 422
X-Request-Id: req_01JA0Q8X2M4N6P8R0T2V4W6Y8Z

{
  "error": {
    "type": "insufficient_balance",
    "message": "Insufficient balance.",
    "request_id": "req_01JA0Q8X2M4N6P8R0T2V4W6Y8Z",
    "retryable": false,
    "details": { "balance": 40, "required": 100 }
  }
}
  • type: a stable code. Match on this. New types may be added over time, so keep a default branch.
  • message: for developers. Its wording can change.
  • request_id: also in the X-Request-Id header. Send it to support.
  • retryable: true for 429 and every 5xx, false for all other statuses. A retryable error should be retried with the same Idempotency-Key (Idempotency); a non-retryable one will fail the same way until you change something.
  • details: optional, extra fields that depend on the type (listed below).

All error types

StatustypeMeaning and what to do
400invalid_requestThe body, query or a header failed validation, the JSON is malformed, or a starting_after cursor is unknown. On live, price or currency next to a receipt_id is refused here (the store's price is recorded), and so is bonus: true next to a receipt_id or a price. A validation failure carries details.issues, a list of path and message for each field (a missing Idempotency-Key shows up here too); a rule the ledger enforces (metadata limits) carries details.field; an unknown starting_after cursor may come without it. Fix the request.
400sale_kind_requiredA credit did not say what it is. Send exactly one kind: receipt_id for an App Store / Google Play sale, price and currency for a web sale, or bonus: true for coins with no sale behind them. Applies on test and live. See How your fee is measured.
400currency_not_supportedLive only: a web sale (a credit with price and currency) must be declared in USD, EUR or a currency of the European Central Bank's reference rates. Send the price in one of those. See How your fee is measured.
400period_out_of_rangeA leaderboard at date is in the future or outside the retained history. Ask for a recent period.
401invalid_api_keySee Authentication.
401invalid_client_tokenSee Authentication. Mint a new token and retry once.
403project_suspendedThe live environment is suspended (details.reason for a secret key; a client token gets the generic message "This app is temporarily unavailable." and no reason). While suspended, a secret key can still read (GET, HEAD) and delete users (DELETE /v1/users/{id}); every other write and all client-token traffic is refused. Test keys are never suspended. Contact support.
403test_limit_reachedA test environment hit its cap. details.limit is users or monthly_operations, details.max the cap. See Rate limits. Use a live key, or reset test data for the user cap.
403room_not_allowedA client token sent a gift with a room_id its rooms claim does not cover. Mint the token with that room or a matching prefix.
403rooms_disabledGift rooms are switched off for the project (battle endpoints).
403plan_requiredThe live environment needs a paid plan (details.plan). On the free Test plan a live key can only read (GET, HEAD), delete users, hide them from leaderboards and revoke client tokens; every other live write is refused, and client tokens get the generic project_suspended. Live gift rooms and battles need a paid plan too. Test keys and test rooms work on every plan. Choose a plan in the dashboard (Plan & billing).
404not_foundThe route, user, gift, gift transaction or client token does not exist in this project and environment. Check the id and that you are using the right key (test or live).
409duplicate_referenceA credit or debit with this reference already exists. details.existing_entry_id points at it. If it is the same purchase, the work is done; otherwise use a new reference.
409gift_existsA gift with this id already exists.
409already_reversedThis gift transaction was already reversed. A gift can be reversed once.
409receipt_already_creditedThis verified receipt already backed a credit (details.credited_at, details.ledger_entry_id). See Receipt validation.
409project_deletion_pendingThe project is scheduled for deletion, so the ledger takes no more writes (deleting a user is the one exception and still works until the purge). Cancel the deletion in the dashboard to continue. The project's keys stop working at once, so most requests see 401 instead; 409 is what a key still in the 30-second key cache gets. See Users and privacy.
409receipts_not_configuredReceipt verification: the project has not set up that store in the dashboard yet (details.reason). See Receipt validation.
409round_supersededA battle start named an earlier round of this room (one of its last 16), for example a late retry after a newer round started. Nothing was changed. Use a new round id to start a round.
410project_deletedThe project was deleted and its data erased. Only a key still in the 30-second key cache sees this; after that the key answers 401.
413payload_too_largeThe request body is over 16 KB.
422insufficient_balanceThe user does not have enough coins. details.balance and details.required. Offer a top-up. Only a debit with allow_negative: true may take a balance below zero.
422balance_limit_exceededThe new balance would be outside the allowed range (more than 1,000,000,000,000,000 in either direction).
422user_deletedThe user was deleted and cannot be changed.
422idempotency_key_reusedThe Idempotency-Key was already used with a different endpoint or body within 24 hours. Use a new key for a new operation, or send the original request again.
422catalog_fullThe catalog already holds the maximum of 500 gifts (details.limit). Archive nothing: archived gifts still count, so reuse an existing gift or edit it instead.
422gift_inactiveThe gift is archived and cannot be sent. Pick an active gift.
422self_gift_not_allowedSender and receiver are the same user.
422receipt_revoked, receipt_price_mismatch, price_missingA receipt-backed credit was refused: the store reported a refund, the price you sent differs from the receipt's, or (live) the Google Play price of the receipt could not be read: give the service account the View financial data permission and verify the receipt again. See Receipt validation.
422receipt_invalid, app_mismatch, environment_mismatch, product_mismatch, receipt_not_found, store_credentials_rejectedReceipt verification refused the purchase; details.reason says exactly why (not authentic, another app, sandbox versus live, another product, unknown to the store, the store rejected the project's credentials). Do not grant the coins. See Receipt validation.
422token_not_revocableThat client token has no id (it was minted before revocation existed). Revoke by user instead.
429rate_limitedToo many requests: over the plan's limit for this project environment, over an end user's limit, or too many failed authentications from one IP address (a valid key is never limited by its IP). Wait Retry-After seconds: 10 for the request limits, up to an hour for the limit on new receivers (details.limit: "new_receivers_per_hour"). Retryable. See Rate limits.
500internal_errorSomething broke on our side. Retryable. Quote the request_id if it persists.
503temporarily_unavailableClient tokens or receipt verification are not available right now, or the project's ledger is momentarily overloaded (nothing was applied). Wait Retry-After seconds when present and retry with the same Idempotency-Key. Retryable.
503store_unavailableReceipt verification: the store (Google Play) did not answer. Retry-After says when to try again. Retryable. See Receipt validation.
503leaderboard_buildingLeaderboards are being built for the first time. details.retry_after and the Retry-After header give the seconds. Retryable.
503rooms_unavailableThe gift room service could not be reached (battle endpoints). Retry.

Receipt verification (POST /v1/receipts/verify) has its own error types, each with a details.reason; the reasons are listed in Receipt validation. Note that receipt_not_found is 404 when a credit names an unknown receipt_id and 422 when the store does not know the purchase during verification.

Handling errors in practice

  • Retry only when retryable is true, with backoff, and always with the same Idempotency-Key. See Rate limits for a loop.
  • A timeout or a dropped connection tells you nothing about whether the request worked. Retry it with the same key: you get the first answer back, never a second change.
  • Show end users your own wording. insufficient_balance usually means "open your coin store".
  • Log type and request_id. Do not log gift message text or metadata.

Questions or something unclear? support@giftingstack.com.