error.code. The message is written for people and may be reworded.
This page lists every code in the API’s error registry. If a close fails halfway, read Recovery first: it explains what state the account is in.
Reading the Retry column
Three rules that apply to every row:
- Every
503and every429carries aRetry-Afterheader. On a503it is a fixed default of 30 seconds, not a measured recovery time. A503does not always mean waiting helps:registry_expiredand the*_not_configuredcodes are marked No because they need the operator to act. - The
statuscolumn is the HTTP status the code is returned with.request_failedis any other 4xx status, anddefi_positions_unavailableis raised as a plan blocker rather than a fixed status. - The SDK retries only the calls and statuses it documents, and never retries
submit.
What to show the user
Show the plain-languagemessage, never the code, the status or a stack trace. When something goes wrong that the user cannot fix, show the error.requestId as a reference they can quote when they ask for help. A message from a blocker or a 422 already names what to do, so display it as written.
Request and authentication
These mean the request itself is wrong or not allowed. Treat them as a fault in the calling code: fix the request and do not resend it unchanged. A user has nothing to act on, so show a short “this request could not be completed” message with the reference id.unauthorized means the API key is missing or invalid, and forbidden means the key may not do this.
Availability and rate limits
These are transient. Retry with backoff, honoringRetry-After. While retrying, show “The service is busy, trying again”. If it keeps failing, show the message and the reference id.
Reading accounts and routes
A failed read is not an answer.destination_read_failed is never reported as a missing destination, and account_read_failed, allowances_read_failed and path_lookup_failed are safe to retry. account_not_found is a real answer: the account does not exist on that network, or it has already been merged. account_too_large means the account has more entries than one request can read.
Planning and building a close
These come fromclose/plan and close/transactions. The message names the problem and usually what resolves it, so show it as written. Several carry details: needs_decisions lists the unanswered decisions in details.missing, a missing transfer destination or conversion floor names its details.decisionId, and merge_destination_unusable lists details.problems. quote_drifted means a conversion route changed between planning and building: plan again and let the user choose again, or pick a transfer, which needs no route. source_sequence_too_far cannot be fixed by the user or the API: the account can be closed only once the network’s ledger count catches up.
DeFi position exits
A position that cannot be closed safely is reported, never skipped. Show themessage, which says what to do in the protocol itself (repay a loan, claim rewards, add a trustline, wait out a cooldown). After the user has done that, analyze the account again and plan again. defi_positions_stale means the detected positions are out of date, so plan again. defi_positions_unavailable appears as a plan blocker when detection could not confirm the account’s positions: analyze again.
Soroban token balances
These refuse a conversion or transfer of a Soroban token that cannot be done safely. Show themessage. Where a conversion is refused, offer a transfer to an account the user names. soroban_token_unreadable is a failed read and is safe to retry.
Submitting a transaction
submit waits up to 90 seconds for confirmation. submit_rejected carries the network’s own reason in details.resultCode, for example tx_too_late for an expired transaction or tx_bad_seq for a stale sequence number. Build and sign a fresh transaction rather than resending the old one. confirmation_timeout and submit_failed do not tell you whether the transaction landed. Look the hash up on an explorer before doing anything else. Resubmitting the identical signed transaction is safe, because the API reports an already confirmed transaction as a success. Never rebuild and sign a second transaction while the first may still confirm. See Recovery.
Exchange relay, sponsored fees and the registry
A refusal here happens before anything is submitted, so nothing has changed on the ledger.transaction_structure_not_allowed means the transaction is not the exact shape the relay or the fee sponsor will sign, and sending it again will not change that. registry_expired and the *_not_configured codes are about the server, not the request: show the message, and offer a destination that is not an exchange where one exists.
Allowance revocation
These come from the allowance endpoints.revoke_needs_restore means ledger entries have to be restored before the revocation can be built. revoke_build_failed is safe to retry.
API keys, operator routes and stats
These belong to key management, the operator routes and the public stats counter.invalid_challenge means the signed sign-in message is invalid or has expired: request a new one. tx_not_verified means the transaction is not a confirmed account merge on that network. Check the hash and the network before sending it again.