> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.walletstech.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.walletstech.com/_mcp/server.

# Errors

Every failure returns the same shape, with the HTTP status repeated in `error` as a string:

```json
{
  "message": "Unknown assetId",
  "error": "400",
  "requestId": "b7a1f0c2-3d4e-4a5b-9c6d-7e8f90a1b2c3"
}
```

`message` is always safe to show a user and safe to log. Internal detail never appears in it — when something breaks on our side, the specifics go to our audit log against the `requestId`, and you get a generic message. **Quote the `requestId` when you report a problem** and we can pull up the exact request.

## Status codes

| Status | Means                                                                                                                       | What to do                                                                                    |
| ------ | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `400`  | Bad input — a missing field, a malformed address, an unknown `assetId`, an unparseable amount, an amount below the minimum. | Fix the request. Retrying it unchanged will fail identically.                                 |
| `401`  | Not authenticated, or a wrong `password` on `/send` and `/extend`.                                                          | Re-authenticate, or fix the password. **Nothing was signed or broadcast.**                    |
| `402`  | The wallet cannot cover the transfer — not enough of the asset, or no native coin to pay gas.                               | Top the wallet up. **Nothing was signed or broadcast.**                                       |
| `403`  | Refused on purpose. `/seed` always returns this; `/extend` returns it when the seed does not derive the user's address.     | Do not retry.                                                                                 |
| `404`  | The transaction hash is not known to the chain.                                                                             | For a fresh broadcast, wait and poll again — it may not have propagated yet.                  |
| `409`  | A conflict — a wallet that already exists, or a seed that cannot be verified against that user.                             | Read the message; usually the user already has what you were creating.                        |
| `422`  | The amount is smaller than the network fee that would be deducted from it (native sends only).                              | Send more, or send a token instead. **Nothing was broadcast.**                                |
| `500`  | Something failed on our side — including a chain node or RPC provider being down or timing out.                             | Safe to retry a read. **Do not blindly retry a `/send`** — see below.                         |
| `502`  | The chain **rejected the broadcast** (Tron). The transfer did not go out.                                                   | Read the message. Do not retry unchanged.                                                     |
| `503`  | **This deployment is not configured for that chain** (`Chain is not configured: SOLANA`).                                   | **Permanent — do not retry.** It is a deployment issue, not an outage. Contact your operator. |

## The two that matter

**`401` on `/send` means the password was wrong — nothing was broadcast**, so fixing it and retrying is completely safe. But a `500` or a timeout on `/send` is ambiguous: the transaction may have been signed and broadcast before the failure reached you. **Never auto-retry it**, and do not assume it failed just because it isn't in `/transactions` yet. Follow the procedure in [Reliability](/get-started/reliability) — retrying blind is how you send twice.

**A `200` from `/send` is not a confirmation.** It returns a `txHash` as soon as the network accepts the transaction. It can still fail on-chain — out of gas, reverted, dropped. Poll `/txid` until `status` is `success` or `failed`, and treat `pending` as unfinished.

## Common messages

| Message                                    | Status    | Cause                                                                                                                                                       |
| ------------------------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `assetId is required`                      | 400       | You omitted `assetId`. There is no default.                                                                                                                 |
| `Unknown assetId: 51`                      | 400       | Not a supported asset — or a mainnet-only asset on a testnet deployment. The offending id is included in the message. See [Asset IDs](/get-started/assets). |
| `Wallet not found for user on this chain.` | 400       | That user has no wallet on the asset's chain — usually because the chain was not configured when the user was created. Backfill with `/extend`.             |
| `Missing/Invalid userId`                   | 400       | `userId` must be a 24-character hex id.                                                                                                                     |
| `Invalid amount`                           | 400       | `amount` must be a decimal string like `"0.5"` — not a float, not base units, not negative.                                                                 |
| `Amount below minimum of 0.0001ETH`        | 400       | Native EVM sends have a floor of `0.0001` (ETH, BNB, POL).                                                                                                  |
| `Minimum send amount is 0.00001 BTC`       | 400       | Bitcoin's floor.                                                                                                                                            |
| `Amount must be greater than zero.`        | 400       | Token sends cannot be zero.                                                                                                                                 |
| `Amount has more than 6 decimal places`    | 400       | More precision than the asset's decimals allow.                                                                                                             |
| `Missing/Invalid address`                  | 400       | A malformed `recipient` (or `sender` on `/fee`).                                                                                                            |
| `Sender and recipient must differ`         | 400       | Self-transfers are rejected.                                                                                                                                |
| `Invalid password`                         | 400 / 401 | The wrong keystore password. **400** on Bitcoin, **401** on Solana.                                                                                         |
| `Invalid credentials`                      | 401       | Bad api key on `/auth`, or the wrong keystore password on `/send` / `/extend` (EVM and Tron).                                                               |
| `Insufficient native funds.`               | 402       | Not enough of the native coin for the transfer.                                                                                                             |
| `Insufficient native for gas.`             | 402       | The token balance is fine, but there is no native coin to pay the gas.                                                                                      |
| `Insufficient token balance`               | 402       | Not enough of the token.                                                                                                                                    |
| `Seed does not match this user.`           | 403       | The mnemonic sent to `/extend` does not derive that user's stored address.                                                                                  |
| `Seed export is disabled.`                 | 403       | `/seed` is permanently disabled. Key export is not available at any tier.                                                                                   |
| `Transaction not found`                    | 404       | Unknown hash, or a broadcast that has not propagated yet.                                                                                                   |
| `Wallet already exists.`                   | 409       | A derived address is already registered.                                                                                                                    |
| `Amount does not cover the network fee.`   | 422       | On a native send the fee is taken **out of** the amount — and this amount is smaller than the fee.                                                          |
| `Transaction was rejected by the network.` | 502       | Tron refused the broadcast. Nothing went out.                                                                                                               |
| `Chain is not configured: SOLANA`          | 503       | This deployment does not run that chain. Permanent.                                                                                                         |

## Idempotency

There is none. `/send` has no idempotency key: **the same request sent twice sends funds twice.**

Most failures are safe — a `400`, `401`, `402`, `422` or `502` means nothing was ever broadcast, so retrying once you have fixed the cause carries no risk. The dangerous case is a `500`, a timeout or a dropped connection: the transaction may have been signed and broadcast *before* the failure reached you.

Do **not** resolve that case by checking `/transactions` and retrying if you don't find the transfer. EVM history comes from an indexer that lags the mempool, so a fresh broadcast is routinely absent — and transaction rows carry no counterparty address, so you cannot positively identify one transfer anyway. "Not found" does not mean "not sent."

[**Reliability**](/get-started/reliability) sets out the procedure that is actually safe, and lists exactly which failures may be retried automatically.