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
| Field | For | Rule |
|---|---|---|
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” |
HTTP — request level
| error_code | HTTP | Retry? | 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 |
503 | new 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
- Same Idempotency-Key → retrying a timeout, a
5xxother thanjob_interrupted, or a429. Safe: you get the original answer back, never a second activation. - New Idempotency-Key → a genuinely new attempt after a definitive failure (e.g. re-running a
failedcode later), or afterjob_interrupted. - Never retry a
4xxunchanged — it will fail identically.