Docs menu

Errors & rate limits

The envelope

json
{ "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" }
FieldContract
codeStable integer. Never renumbered, never reused for a different meaning. New conditions get new numbers.
slugStable lowercase snake_case name for the same condition. One-to-one with code.
messageEnglish, human-readable, may change without notice. Show it to a human, never parse it.
fieldThe offending request field for validation errors, otherwise null.
retryableWhether retrying the identical request could plausibly succeed later.
detailsOptional machine-readable context; shape is specific to the code, documented below where it exists.
request_idAlso 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

CodeSlugHTTPWhenRetry
1001missing_credentials401One 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.
1002invalid_signature401X-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.
1003signature_timestamp_skew401X-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.
1004ip_not_allowed401Source 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.
1005insufficient_scope403Key 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.
1006key_revoked401The key was deleted or expired.No.
1007key_inactive401The key exists but is switched off.No.
1008account_suspended403The account may read but not order.No. Contact support.
1009replayed_signature401This 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.
1010challenge_invalid401The 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.
1011bootstrap_token_expired401The 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.
1012key_environment_mismatch401The 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.
1013session_expired401No dashboard session cookie, or the session behind it expired or was signed out.No — sign in again by signing a fresh challenge.
1014csrf_token_invalid403A 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.
1100rate_limited429Per-key or per-IP request budget exceeded. Retry-After says how long; details.scope is key or ip.Yes — honour Retry-After, then exponential backoff with jitter. Never a tight loop.
1101concurrency_limited429Too 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.
1102quota_exceeded429A daily or monthly cap on the account was reached. details.resets_at.Yes, after resets_at. Retrying before that cannot help.
2000malformed_json400Body is not valid JSON, or the content type is not application/json.No.
2001validation_failed400A 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.
2002empty_patch422A PATCH with no changeable field in the body.No.
2003tier_unavailable422Tier syntactically valid but not currently sellable for this resource. details.available_tiers lists what is. Also the energy 1d tier while it is switched off (TIER_1D_ENABLED=false) on POST /v1/orders, batches, quotes and estimates; GET /v1/prices shows that row with available: false.No.
2004quote_mismatch422quote_id sent alongside explicit order fields that contradict the quote.No.
2005idempotency_key_required400A batch created with neither client_batch_id nor an Idempotency-Key header.No.
2006duplicate_receiver400The same address appears twice in one batch. Merge the amounts instead.No.
2007invalid_address400Not a valid Base58Check TRON address (bad checksum, wrong length, hex form instead of Base58).No.
2008amount_out_of_range400Below the tier’s minimum or above its maximum. details.min_amount / details.max_amount.No.
2009batch_too_large400More than 100 receivers, or the total across receivers exceeds the per-batch ceiling.No.
2010invalid_webhook_url400Not HTTPS, resolves to a private/loopback/link-local address, carries credentials, or exceeds 2048 characters.No.
2011unsupported_contract422The contract in a transfer estimate is not a TRC-20 token we can simulate.No.
3001order_not_found404No such order, or it belongs to another account. The two are deliberately not distinguished.No.
3002order_not_cancellable409A 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.
3003receiver_is_ours422The receiver is one of the platform’s own addresses.No.
3004receiver_not_activated422Receiver not activated and activate: false was sent. Nothing was charged.Yes, with activate: true, or after activating the address yourself.
3005quote_expired409The quote’s expires_at has passed.Yes — take a fresh quote first.
3006price_above_limit409Live 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.
3007nothing_to_reclaim409The order never resulted in an active delegation, or it is already expired and returned.No.
3008reclaim_unavailable409The order was filled from a third-party provider, whose resources we cannot return. details.filled_by says which.No.
3009order_in_terminal_state409A state change was requested on an order that is already finished.No.
3010idempotency_conflict409The 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.
3011subscription_exists409This address already has a subscription for this resource. details.subscription_id.No — patch the existing one.
3012subscription_not_active409An operation needing a live subscription hit a cancelled one.No.
3013webhook_limit_reached409Two endpoints already registered.No — delete or patch one.
3014webhook_role_taken409The requested role is occupied.No — PATCH the other endpoint to swap roles.
3015request_in_progress409An 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.
3016account_unfunded409Unused 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.—
3017api_key_limit_reached409The 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.
3018deposit_rotation_limited429Operator 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.
3019address_book_full409POST /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.
3020subscription_rule_invalid422POST / 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.
3021subscriptions_unavailable409Subscriptions are switched off on this deployment (SUBSCRIPTIONS_ENABLED=false): 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.
4001insufficient_funds402Balance 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.
4002balance_reserved402Nominal balance sufficient but too much is held against in-flight orders.Yes, once the in-flight orders settle.
4003currency_not_supported422The brand does not accept this currency for this operation.No.
4004ledger_conflict409Two 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.
5001insufficient_supply503Neither our inventory nor the provider cascade could cover the amount at an acceptable cost. Nothing was charged.Yes, with backoff — supply returns as rentals expire. For a short tier, falling back to a longer tier often succeeds immediately.
5002delegation_failed503The 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.
5003chain_unavailable503Our TRON node is not answering, so we cannot verify or deliver.Yes, with backoff.
5004provider_unavailable503Every fallback provider declined or timed out and our own inventory was short. Nothing was charged.Yes, with backoff.
5005receiver_capacity_exceeded422The 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.
5006supply_paused503Sales 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.
5007capacity_unavailable503Own capacity was short and this order may not overflow to a provider: an on_demand contract, or an on_demand_guaranteed contract past its daily overflow cap. The charge is refunded and the order ends failed. details.supply_policy, details.contract_id.Yes, with backoff — own capacity returns as rentals expire.
9000internal_error500An 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.
9001timeout504The operation exceeded the gateway’s window; outcome unknown.Yes, same client_order_id.
9002not_implemented501The endpoint exists in this contract but not yet in this deployment (phase 1 does not ship everything).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 is fail-secure — nothing was stored and nothing was charged, so retrying the identical request with the same client_order_id is always 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.

Compatibility

The CatFee-, Netts-, TronZap- and FeeSaver-compatible facades translate these codes into each competitor’s numbering; each comparison page under /compare maps them. The native codes above are the source of truth; a facade’s code is a projection and is lossy in places, which each compat document states explicitly.

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 ones actually in force for your key, and wholesale accounts get higher ones on request:

GroupLimit
Order creation (POST /v1/orders, POST /v1/batches)30 rps
Reads (GET on orders, quotes, prices, resources)50 rps
Account, API keys, webhooks management5 rps

Next steps

    ↑ ↓ to move · Enter to open · Esc to close