Guide

Errors

Branch on error_code. Keep technical text away from your end customer.

Error body

Every error — at any status — has the same shape:

{ "ok": false, "error_code": "bad_player", "message": "player_id must be digits." }
Status codes are meaningful: 4xx will not succeed on retry, 429 and 5xx will. A business verdict such as “code already used” is never a 5xx, so your HTTP client will not retry something that can never change.

What to show whom

FieldForRule
error_code Your bot logic Stable string — switch / map to your own user-facing copy.
message You / support only Short technical hint that may change without notice. Log it. Do not paste it to the player in your shop bot.
request_id Support tickets On every response, also in the X-Request-Id header. Store it.
Your UI text End customer Friendly only: wrong ID, try again later, contact support — never raw API payloads.
Forward compatibility: new error_code and status values may be added at any time. Treat an unrecognised value as a generic failure and log it — never crash, and never assume the lists below are closed.

Per code — status

Inside a 200/422 redeem response, each entry in codes[] carries its own outcome. You pay for outcomes, not attempts — anything caused by our infrastructure is refunded automatically.

status error_code Your bot Billed Show customer
activated — Mark the order delivered ✅ “Done — UC delivered”
code_used CODE_USED Do not retry; the code is gone (disposition: deleted) ✅ “Code already used”
invalid_code INVALID_CODE Do not retry the same code; remove it from your stock ✅ “Code invalid”
bad_player CHARACTER_NOT_FOUND Ask for the correct player ID and start a new request ❌ refunded “Player ID not found”
failed ACTIVATION_FAILED Temporary issue on our side. On stock codes the code comes back returned — retry later with a new Idempotency-Key. ❌ refunded “Temporary issue — we’ll retry”
Always pair status with disposition before you touch your own inventory — see Redeem → disposition.

HTTP — request level

error_codeHTTPRetry?What to do
unauthorized · revoked · expired 401❌Fix / rotate the key
ip_denied · scope_denied · unknown_customer 403❌Key lacks the IP allowance or scope
subscription_expired 402❌ Only when GET /v1/me reports subscription.required: true. Renew the bot subscription. Body carries subscription_expired_at. While the bot is free this error is never returned — do not treat active: false as an API-plan problem; check plan_active instead.
no_plan 402❌ No API plan on this account — message @Nogitsuneiii.
insufficient_funds 402❌ Top up the wallet in Telegram (/wallet), then retry.
bad_request · bad_player · bad_codes · bad_packs · no_combo · empty · ambiguous_source · idempotency_required · https_required · webhook_url_blocked 400❌ Fix the request. Webhook URLs must be public https:// (not localhost / private IPs).
not_found 404❌Unknown path or job id
no_stock 409❌Add codes, or switch to source A. Nothing is billed.
idempotency_in_progress 409⏳ An identical request is still running — wait, then poll instead of resending
idempotency_conflict 422❌ Same key reused with a different body — use a new key
payload_too_large 413❌Body over 256 KB — send fewer codes
quota_exceeded · rate_limited 429✅ Wait for Retry-After — see Limits
unavailable 503✅Service temporarily unavailable — retry with backoff, same Idempotency-Key.
job_interrupted 503new key Async job stopped by a server restart. Unused stock and quota were restored. Do not replay the old 202 — start a new attempt with a new Idempotency-Key.
internal 500✅ Retry with the same Idempotency-Key — the key is released on 5xx that never finished
422 means two different things — check the body. {"ok": false, "error_code": "idempotency_conflict"} is a request error; a body with a codes array is a normal redeem result where no code activated.

Retry rules, in short