Errors & rate limits
The envelope
{ "error": { "code": 4001, "slug": "insufficient_funds",
"message": "Required 1300000 SUN, available 420000 SUN",
"field": null, "retryable": true,
"details": { "required_sun": 1300000, "available_sun": 420000 } },
"request_id": "req_01J9Z5P8T3WQ7X" }
| Field | Contract |
|---|---|
code | Stable integer. Never renumbered, never reused for a different meaning. New conditions get new numbers. |
slug | Stable lowercase snake_case name for the same condition. One-to-one with code. |
message | English, human-readable, may change without notice. Show it to a human, never parse it. |
field | The offending request field for validation errors, otherwise null. |
retryable | Whether retrying the identical request could plausibly succeed later. |
details | Optional machine-readable context; shape is specific to the code, documented below where it exists. |
request_id | Also in the X-Request-Id response header. Quote it in support requests. |
Branch on slug or code, never on the HTTP status alone — several distinct conditions share a status (409 covers both an idempotency conflict and an order that cannot be reclaimed, and they call for opposite reactions). Unknown codes will appear: treat one you do not recognise as its status class (4xx “my fault, do not retry blindly”, 5xx “their fault, retry with backoff”). Do not fail closed on an unrecognised slug.
Ranges: 1000–1099 auth/credentials · 1100–1199 rate limiting and quotas · 2000–2099 request validation · 3000–3099 business state · 4000–4099 money · 5000–5099 supply · 9000–9099 internal.
Codes
| Code | Slug | HTTP | When | Retry |
|---|---|---|---|---|
| 1001 | missing_credentials | 401 | One or more of X-API-KEY, X-API-TIMESTAMP, X-API-SIGN absent. A malformed header value (bad base64, wrong length) is 1002, not 1001; 1001 means the header was not sent at all. | No — fix the client. |
| 1002 | invalid_signature | 401 | X-API-SIGN does not match the canonical string the server computed. | No. Re-read the canonical string definition; usual causes are re-encoding the query, signing a pretty-printed body while sending a compact one, or a trailing newline. |
| 1003 | signature_timestamp_skew | 401 | X-API-TIMESTAMP more than 5 s from server time, either direction. details.server_time carries our clock. | Yes, once, after fixing the clock. Run NTP; do not widen the retry loop. |
| 1004 | ip_not_allowed | 401 | Source address not on this key’s allowlist. details.source_ip echoes what we saw. | No. Add it in the dashboard; behind NAT or an egress pool, allowlist the whole block. |
| 1005 | insufficient_scope | 403 | Key valid but lacks the scope this endpoint needs. details.required_scope names it in the one platform permission vocabulary (area.action, e.g. orders.create), documented in openapi.yaml. | No — a key’s scopes are chosen explicitly by the account owner, so widening one is a deliberate act, never an automatic retry. |
| 1006 | key_revoked | 401 | The key was deleted or expired. | No. |
| 1007 | key_inactive | 401 | The key exists but is switched off. | No. |
| 1008 | account_suspended | 403 | The account may read but not order. | No. Contact support. |
| 1009 | replayed_signature | 401 | This exact signature was already used; signatures are single-use within the skew window. | No — generate a fresh timestamp and signature per attempt, including per retry. |
| 1010 | challenge_invalid | 401 | The signup/login challenge could not be accepted: unknown nonce, expired nonce, nonce already used, or a signature that does not recover the address. One code for all four on purpose — a caller who has not proved control of an address learns nothing about which addresses have accounts. details.reason is "invalid" and carries no detail. | Yes, once, after a fresh POST /v1/accounts/challenge. |
| 1011 | bootstrap_token_expired | 401 | The 15-minute bootstrap token from POST /v1/accounts/challenge/verify expired or was used on an operation it does not open (only account creation, the signup deposit address, GET /v1/account and the first key). | Yes — re-run the challenge and verify. |
| 1012 | key_environment_mismatch | 401 | The key’s environment marker does not match the network this host serves: an ak_live_… key sent to the test host, or an ak_test_… key sent to the live one. details.key_environment and details.host_environment name both. Answered before the key is looked up, so it says nothing about whether the key exists. | No — use the key issued for this host. |
| 1013 | session_expired | 401 | No dashboard session cookie, or the session behind it expired or was signed out. | No — sign in again by signing a fresh challenge. |
| 1014 | csrf_token_invalid | 403 | A cookie-authenticated write arrived without an X-CSRF-Token header matching the session’s CSRF cookie. | No — read the CSRF cookie and send it on every write. |
| 1016 | login_code_invalid | 400 | The e-mail sign-in code does not match. details.attempts_left is how many guesses this code has left (5 in all). | Yes, with the right code; at zero, request a new code. |
| 1017 | login_code_expired | 400 | No live code for this e-mail: never sent, older than 10 minutes, replaced by a newer one, already used, or out of attempts. | Yes — request a new code. |
| 1018 | identity_taken | 409 | The e-mail (or other identity) being linked already signs in to another member of this account. One of another account moves here when that account is empty, else 1023. | No — write to support. |
| 1019 | last_identity | 409 | Unlinking this sign-in method would leave the account with no way in. | No — link another method first. |
| 1020 | step_up_required | 403 | The signed-in person has 2-step on (GET /v1/session/step-up) and this action — withdrawal, API key creation, linking or unlinking a sign-in method, changing the 2-step channel — needs a confirmation. details.step_up_id, details.channel (email, telegram or wallet), details.expires_at (5 minutes), details.message (what is being approved; for wallet, the exact text to sign). For email a code naming the action has just been mailed (details.address: to which of the person’s addresses); for telegram our bot has just sent it. | Yes — approve with POST /v1/step-up/{id}/approve, then repeat the identical request with X-Step-Up: <step_up_id>. |
| 1021 | step_up_invalid | 403 | The approval cannot be used: unknown, not approved yet, expired, already used, made for another action or another payload (amount, address), or a wallet signature that does not recover a wallet linked to this person. | No — drop X-Step-Up and repeat the request for a fresh confirmation. |
| 1022 | step_up_channel_unavailable | 409 | The 2-step channel cannot be chosen: email needs a verified e-mail sign-in method, wallet a linked wallet, telegram a linked Telegram account and the bot configured on this deployment. details.reason: delivery_failed: Telegram did not take the code (retry shortly). | No — link the method first. |
| 1023 | identity_in_use_account_not_empty | 409 | The sign-in method being linked belongs to another account that holds something: details.reason is balance, reserved, orders, withdrawals, api_keys, webhooks, node_keys, members, subscriptions or oauth_grants. A method of an EMPTY account is moved instead and that account is closed. | No — write to support. |
| 1024 | oauth_token_invalid | 401 | The OAuth access token (Authorization: Bearer oat_…) is unknown, expired or revoked; its connected agent was revoked or expired; it was issued for another resource; or the person who connected the agent is no longer a member. Sent with WWW-Authenticate: Bearer error="invalid_token". | No — refresh the token, or connect the agent again. |
| 1025 | telegram_bot_not_started | 409 | Our bot could not write to the person in Telegram (a step-up code, or the notice that Telegram confirmation is on): Start never pressed in the bot, or the bot blocked. details.bot_username names the bot; nothing changed. | Yes — open the bot in Telegram, press Start, then repeat. |
| 1026 | telegram_relink_needed | 409 | Telegram confirmation was chosen, or a code is due through it, but the Telegram account was linked before we stored the Telegram user id our bot writes to (details.channel: telegram). Nothing changed. | No — link Telegram again (POST /v1/session/identities/telegram/start; the same Telegram account just refreshes), then repeat. |
| 1027 | last_owner | 409 | The signed-in person tried to leave an account they are the last owner of (dashboard only). Nothing changed. | No — appoint another owner first, then leave. |
| 1028 | mail_unavailable | 503 | An e-mail code was asked for (POST /v1/session/email/start, POST /v1/session/identities/email/start) but this deployment cannot deliver mail right now: no mail provider is configured, or the provider refused the message. No code was issued. | Yes, later — or sign in another way. |
| 1029 | captcha_failed | 403 | An e-mail sign-in code was asked for (POST /v1/session/email/start) without a passed bot check: captcha_token is missing, used, expired or from another site. Only when GET /v1/session/methods names email_captcha. No code was issued. | Yes, after passing the check again. |
| 1030 | admin_confirmation_required | 428 | Admin panel only: this action needs a signature by the admin’s confirmation key. details.confirm_id and details.message carry the challenge and the exact text to sign (docs/product/admin-auth.md §6). Nothing was changed. | Yes — sign details.message and repeat the same request with X-Admin-Confirm-Id and X-Admin-Confirm-Signature. |
| 1031 | admin_confirmation_invalid | 403 | Admin panel only: the confirmation is unknown, already used, expired, issued for another action, or not signed by the confirmation key. Nothing was changed. | Yes, with a new confirmation. |
| 1032 | admin_session_invalid | 401 | Admin panel only: the admin session is unknown, ended, frozen, or the request’s browser-key signature, time or nonce did not check out. | Sign in to the admin panel again. |
| 1100 | rate_limited | 429 | Per-key or per-IP request budget exceeded. Retry-After says how long; details.scope is key or ip, or step_up when an unused confirmation code went to the same place under 15 s ago (details.retry_after; a used code never holds the next one back). | Yes — honour Retry-After, then exponential backoff with jitter. Never a tight loop. |
| 1101 | concurrency_limited | 429 | Too many of this account’s orders or batches in flight at once. | Yes, after the in-flight ones settle. Lower your parallelism rather than retrying harder. |
| 1102 | quota_exceeded | 429 | A daily or monthly cap on the account was reached. details.resets_at. | Yes, after resets_at. Retrying before that cannot help. |
| 2000 | malformed_json | 400 | Body is not valid JSON, or the content type is not application/json. | No. |
| 2001 | validation_failed | 400 | A field is missing, of the wrong type, or out of range. field names it; details.constraint describes the rule. Also returned when scopes is omitted from an API key creation — the API does not invent a default permission set. | No. |
| 2002 | empty_patch | 422 | A PATCH with no changeable field in the body. | No. |
| 2003 | tier_unavailable | 422 | Tier syntactically valid but not currently sellable for this resource. details.available_tiers lists what is. Also any energy term not on sale (currently everything except 1h) on POST /v1/orders, batches, quotes and estimates; GET /v1/prices shows such a row with available: false. | No. |
| 2004 | quote_mismatch | 422 | quote_id sent alongside explicit order fields that contradict the quote. | No. |
| 2005 | idempotency_key_required | 400 | A batch created with neither client_batch_id nor an Idempotency-Key header. | No. |
| 2006 | duplicate_receiver | 400 | The same address appears twice in one batch. Merge the amounts instead. | No. |
| 2007 | invalid_address | 400 | Not a valid Base58Check TRON address (bad checksum, wrong length, hex form instead of Base58). | No. |
| 2008 | amount_out_of_range | 400 | Below the tier’s minimum or above its maximum. details.min_amount / details.max_amount. | No. |
| 2009 | batch_too_large | 400 | More than 100 receivers, or the total across receivers exceeds the per-batch ceiling. | No. |
| 2010 | invalid_webhook_url | 400 | Not HTTPS, resolves to a private/loopback/link-local address, carries credentials, or exceeds 2048 characters. | No. |
| 2011 | unsupported_contract | 422 | The contract in a transfer estimate is not a TRC-20 token we can simulate. | No. |
| 3001 | order_not_found | 404 | No such order, or it belongs to another account. The two are deliberately not distinguished. | No. |
| 3002 | order_not_cancellable | 409 | A batch cancel (POST /v1/batches/{id}/cancel) arrived after that receiver had been picked up. There is no single-order cancel in v1 — POST /v1/orders charges synchronously, so an order never waits unpaid in a queue and this code cannot arise outside a batch. | No — inspect the order; it is probably already delivered. |
| 3003 | receiver_is_ours | 422 | The receiver is one of the platform’s own addresses. | No. |
| 3004 | receiver_not_activated | 422 | Receiver not activated and activate: false was sent. Nothing was charged. | Yes, with activate: true, or after activating the address yourself. |
| 3005 | quote_expired | 409 | The quote’s expires_at has passed. | Yes — take a fresh quote first. |
| 3006 | price_above_limit | 409 | Live total exceeds max_price_sun. details.quoted_total_sun is what it would have cost. Nothing was charged. | Yes, later — the price moves with the pricing period. Or raise the cap. |
| 3007 | nothing_to_reclaim | 409 | The order never resulted in an active delegation, or it is already expired and returned. | No. |
| 3008 | reclaim_unavailable | 409 | This order cannot be returned early. | No. |
| 3009 | order_in_terminal_state | 409 | A state change was requested on an order that is already finished. | No. |
| 3010 | idempotency_conflict | 409 | The same client_order_id / client_batch_id / Idempotency-Key reused with a different body. details.original_id points at the first request’s object. | No — a caller-side bug. Either retry verbatim or pick a new key. |
| 3011 | subscription_exists | 409 | This address already has a subscription for this resource. details.subscription_id. | No — patch the existing one. |
| 3012 | subscription_not_active | 409 | An operation needing a live subscription hit a cancelled one. | No. |
| 3013 | webhook_limit_reached | 409 | Two endpoints already registered. | No — delete or patch one. |
| 3014 | webhook_role_taken | 409 | The requested role is occupied. | No — PATCH the other endpoint to swap roles. |
| 3015 | request_in_progress | 409 | An identical request with this idempotency key is still executing. Retry-After suggests when to look again. | Do not retry the create. Wait, then read the object by its client id. A retry risks nothing but wastes a slot. |
| 3016 | account_unfunded | 409 | Unused since 2026-09-26 — no route returns it. It was POST /v1/api-keys on an account with no confirmed deposit; an unfunded account may now create keys and is refused at POST /v1/orders with 4001 instead. Kept so the number is never reused. | — |
| 3017 | api_key_limit_reached | 409 | The account already holds the maximum number of active API keys. A product limit, not an access right. details.limit and details.used. | No — revoke a key you no longer use; the slot is free immediately. |
| 3018 | deposit_rotation_limited | 429 | Operator surface only: POST /v1/admin/accounts/{id}/deposit-address/rotate inside the rotation limits (default 1 per 7 days, 3 per 90 days). details.retry_after (seconds), details.next_allowed_at; Retry-After header. | Yes — after retry_after. |
| 3019 | address_book_full | 409 | POST /v1/account/addresses with a new address on an account whose address book already holds 100 entries. A product limit, not an access right. details.limit and details.used. Re-saving an address already in the book only relabels it and never hits this. | No — delete an entry you no longer use; the slot is free immediately. |
| 3020 | subscription_rule_invalid | 422 | POST / PATCH /v1/subscriptions with a reserve, low and high that break the rule: 131,000 ≤ reserve ≤ 5,240,000, each a multiple of 1,000, low ≥ 65,000, low < high ≤ reserve, high − low ≥ 65,000 (Basic’s low = high = reserve = 131,000 is the one exception). details.violations names each broken rule. | No — fix the numbers or pick a preset. |
| 3021 | subscriptions_unavailable | 409 | Subscriptions are not available yet: POST /v1/subscriptions, and a PATCH that changes the rule, preset or refill parameters. Reading, pausing, resuming and cancelling existing subscriptions keep working. GET /v1/subscriptions/plans → available says which state is in force. | Yes, later — once available is true. |
| 3022 | withdrawals_unavailable | 409 | Withdrawals are not available. | No. |
| 3023 | withdrawal_in_progress | 409 | A withdrawal is already on its way; one at a time. | After it completes. |
| 3024 | withdrawal_destination_not_allowed | 422 | A withdrawal goes only back to a wallet that topped the account up, and at most what it paid in. details.available_sun is what that wallet can take. | No. |
| 3025 | node_key_daily_limit_reached | 409 | The node key has spent its daily_limit_sun for this UTC day; the proxy buys no more energy with it and refuses the broadcast. details: daily_limit_sun, spent_today_sun, price_sun. | After 00:00 UTC, or with a higher limit on a new key. |
| 3026 | api_key_spend_limit_reached | 409 | The API key has spent its daily (UTC day) or monthly (UTC month) limit on orders. details: period (day/month), limit_sun, spent_sun, price_sun. | After the period turns, or after the limit is raised (PATCH /v1/api-keys/{id}). |
| 3027 | promo_not_found | 404 | POST /v1/account/promo with a code that does not exist. Codes are upper-case letters, digits and -. | No — check the code. |
| 3028 | promo_unavailable | 409 | The code exists but cannot be used now. details.reason: inactive, not_started (with details.starts_at), ended, used_up. | not_started: yes, from starts_at; otherwise no. |
| 3029 | oauth_request_not_found | 404 | No live agent authorization request with this id: unknown, already approved or denied, or older than ten minutes. | No — start the connection from the agent again. |
| 3030 | oauth_grant_not_found | 404 | No live connected agent with this id on this account: unknown, revoked or expired. | No. |
| 3031 | spend_limit_reached | 403 | The connected agent has spent its daily (UTC day) or monthly (UTC month) limit. details: period (day or month), limit_sun, spent_sun, price_sun, resets_at. Not an OAuth error: connecting again does not raise it. | No — after resets_at, or after the person raises the limit in the dashboard. |
| 3032 | agent_key_limit_exceeded | 403 | An AI agent tried to create or edit an API key with a spend limit above its own, or without a limit while it has one. Also a key an agent created (or a key that key created): it is held to the same agent’s limits, and once that agent’s access is revoked or expired it cannot create or widen spending keys at all. details: grant_id, grant_daily_limit_sun, grant_monthly_limit_sun; or grant_id, grant_revoked: true. | No — omit the limits to get the agent’s own, or ask the account owner to set other limits in the dashboard. |
| 3033 | refills_unavailable | 409 | POST /v1/refills while no refill venue is enabled on this deployment. | No — refills are not available yet. |
| 3034 | refill_not_found | 404 | No such refill, or it belongs to another account; the two are not distinguished. | No. |
| 3035 | no_base_resource | 422 | POST /v1/refills for a receiver with no energy (or bandwidth) limit of its own or delegated to it: a refill restores used resource only. | No — the receiver needs a stake or a delegation first. |
| 3036 | nothing_to_refill | 422 | POST /v1/refills for a receiver that has used nothing now, or less than the smallest refill. (Asking for more than it has used is 2008 amount_out_of_range with details.restorable_max.) details.restorable_max (0 = nothing used), details.used, details.min_amount. | Yes — once the receiver has spent at least min_amount. |
| 3037 | promo_not_redeemed | 404 | GET /v1/promo/redemptions/{address} for an address that has not redeemed the promo token. | No. |
| 3038 | promo_not_eligible | 403 | The promo token redeem for an address we never dropped the token on (a token received from another holder does not count). | No. |
| 3039 | promo_already_redeemed | 409 | The address already claimed its grant, by signature or by forwarding the token. details.method, details.redeemed_at. | No — one grant per address. |
| 3040 | promo_redeem_unavailable | 409 | The promo is not open now: details.state is not_started, ended, cap_reached_today (with details.resets_at) or off (no promo on this network). Nothing is recorded. | Yes — after resets_at, or once the promo is open. |
| 4001 | insufficient_funds | 402 | Balance below the order total plus activation. details.required_sun, details.available_sun. | Yes — deposit, then retry with the same client_order_id. That is exactly what the idempotency key is for. |
| 4002 | balance_reserved | 402 | Nominal balance sufficient but too much is held against in-flight orders. | Yes, once the in-flight orders settle. |
| 4003 | currency_not_supported | 422 | The brand does not accept this currency for this operation. | No. |
| 4004 | ledger_conflict | 409 | Two concurrent charges raced on the same balance and one lost. Nothing was charged. | Yes, immediately, same idempotency key. Serialise charges per account if you see this often. |
| 5001 | insufficient_supply | 503 | Not enough energy is available for this amount right now. Nothing was charged. | Yes, with backoff — supply returns as rentals expire. For a short tier, falling back to a longer tier often succeeds immediately. |
| 5002 | delegation_failed | 503 | The delegation transaction was built but the chain rejected it or it never confirmed. Any charge is reversed; the order ends failed with refunded_amount_sun set. | Yes — a fresh client_order_id, since the old order is terminal. |
| 5003 | chain_unavailable | 503 | Our TRON node is not answering, so we cannot verify or deliver. | Yes, with backoff. |
| 5004 | provider_unavailable | 503 | Supply did not answer in time. Nothing was charged. | Yes, with backoff. |
| 5005 | receiver_capacity_exceeded | 422 | The receiver cannot accept this much more delegated resource (TRON limits delegations per address). details.max_additional. | No, not as sent — reduce the amount or wait for existing delegations to expire. |
| 5006 | supply_paused | 503 | Sales administratively paused for this resource or tier. details.resource, details.tier. | Yes, later, but not soon — GET /v1/prices will not list a paused tier. |
| 5007 | capacity_unavailable | 503 | Not enough capacity under your contract’s supply policy. The charge is credited back and the order ends failed. details.supply_policy, details.contract_id. | Yes, with backoff — own capacity returns as rentals expire. |
| 9000 | internal_error | 500 | An unhandled failure. The order’s state is unknown from the response alone. | Yes — with the same client_order_id, the only way to learn whether the first attempt took effect. Never retry a create without one. |
| 9001 | timeout | 504 | The operation exceeded the gateway’s window; outcome unknown. | Yes, same client_order_id. |
| 9002 | not_implemented | 501 | The endpoint is described in the contract but not served yet. | No. |
Validation codes (2000–2099) are all retryable: false and carry field where a single field is at fault; fix the request, retrying it unchanged fails identically.
The supply guarantee: a 503 from order creation never costs you money: either nothing was charged (5001, 5004), or the order ends failed and the charge is credited back (5007). Retrying with the same client_order_id is safe. Where a charge happened and delivery then failed (5002), the order exists, is terminal, and the money is credited back: read it and start a new order rather than retrying the old id.
Retry policy
Retry on 429, 5xx, and on 4001/4002/4004 after fixing the cause. Do not retry any 400, 401, 403, 404, or a 409 other than 4004 — those need a changed request. Always send client_order_id on order creation so every retry is free of double-purchase risk. Exponential backoff starting at 250 ms with full jitter, delay capped at 30 s, give up after roughly 60 s of wall-clock time rather than a fixed attempt count — then read the order by its client id to find out what happened. Honour Retry-After whenever present; it overrides your own schedule.
HTTP status map
200 success, also an idempotent replay of a create and a reclaim that had already happened · 201 a new object was created · 202 accepted for asynchronous processing (batches, and a reclaim whose hash had not landed yet — nothing delivered) · 204 success with no body (deletes, cancels) · 400 malformed or invalid request · 401 credentials missing, wrong, stale or from a disallowed IP · 402 not enough money · 403 authenticated but not permitted · 404 no such object for this account · 409 conflicts with current state, including idempotency conflicts · 422 well-formed but semantically impossible · 429 rate or quota limit · 500 unhandled internal failure · 501 contract-defined but not deployed · 503 temporarily unable to serve, fail-secure on creates · 504 upstream timeout, outcome unknown.
Rate limits
Per API key, and additionally per source IP. Every response carries
RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds). Exceeding a limit
returns HTTP 429 with 1100 rate_limited and a Retry-After header.
Default budgets (per key, per second). The dashboard shows the budgets in force for your key; higher limits on request:
| Group | Limit |
|---|---|
Order creation (POST /v1/orders, POST /v1/batches) | 30 rps |
Reads (GET on orders, quotes, prices, resources) | 50 rps |
| Account, API keys, webhooks management | 5 rps |