> 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.

# Reliability

Three properties of this API shape any serious integration. None of them is a bug; all of them are things you must build around.

## There are no webhooks

The service never calls you. There are no callbacks, no subscriptions, no push notifications of any kind. **Everything you learn about a wallet, you learn by asking.**

That has two consequences.

**Incoming deposits are your problem to detect.** Nobody will tell you a user received funds. You find out by polling `/balance` or `/transactions` for that user and asset. Note the shape of that cost: it is one call *per user, per asset*. With 16 assets and 10,000 users, a naive full sweep is 160,000 requests. Poll only the assets you actually support, stagger users across the interval, and back off hard on wallets that never change.

**Outgoing transfers must be followed up.** A `200` from `/send` means the network accepted the transaction, not that it succeeded.

## Confirming a send

Poll `/txid` with the `txHash` until `status` leaves `pending`:

```json
{ "result": { "hash": "0x…", "type": "send", "amount": "25.5",
              "fee": "0.000315", "status": "success", "date": "2026-07-13T09:14:22.000Z" } }
```

`status` is `pending`, `success` or `failed`. While pending, `date` is `null`.

Sensible intervals, given how fast each chain settles:

| Chain        | Poll every | Give up after                                |
| ------------ | ---------- | -------------------------------------------- |
| Solana       | \~2s       | \~2 min                                      |
| BNB, Polygon | \~5s       | \~10 min                                     |
| Ethereum     | \~15s      | \~30 min                                     |
| Tron         | \~5s       | \~10 min                                     |
| Bitcoin      | \~60s      | hours — a low-fee tx can sit for a long time |

A transfer that stays `pending` past those windows is stuck or dropped. **There is no `/cancel`, no replace-by-fee and no rebroadcast endpoint** — you cannot fix it through this API. Escalate to your operator.

## `/transactions` is a feed, not a ledger

`/transactions` takes only `userId` and `assetId`. There is **no pagination, no cursor, no date range, no page size** — it returns roughly the last **10** transactions, and that number is set server-side, not by you.

**Older history is unreachable.** Once a transaction falls off the end of that window, this API will never show it to you again. If you need statements, reconciliation or an audit trail, you must persist transactions on your side as you see them, keyed on `hash`.

Treat `/transactions` as "what happened recently", and your own database as the record.

## Retrying a `/send` safely

**There is no idempotency key. The same `/send` sent twice sends the funds twice.**

Most failures are unambiguous and safe — the transfer was rejected before anything was signed:

| Outcome                                    | Was anything broadcast?   | Safe to retry?                 |
| ------------------------------------------ | ------------------------- | ------------------------------ |
| `400` bad input                            | No                        | Yes, once fixed                |
| `401` wrong password                       | No                        | Yes, once fixed                |
| `402` insufficient funds                   | No                        | Yes, once funded               |
| `422` amount below the fee                 | No                        | Yes, with a larger amount      |
| `502` broadcast rejected                   | No — the chain refused it | Only after reading the message |
| `503` chain not configured                 | No                        | **Never** — permanent          |
| **`500`, a timeout, a dropped connection** | **Unknown**               | **No — reconcile first**       |

That last row is the dangerous one: the transaction may have been signed and broadcast *before* the failure reached you.

### The reconciliation procedure

**"Not found in `/transactions`" does not mean "not sent."** On EVM chains, history is served by an indexer that lags the mempool — a transfer broadcast seconds ago is routinely *absent*. And a transaction row carries no counterparty address (just `hash`, `type`, `amount`, `fee`, `status`, `date`), so you cannot positively match a specific transfer to a specific recipient. Retrying because you didn't find it is exactly how you send twice.

Do this instead:

1. **Write an intent record before you call `/send`** — user, asset, recipient, amount, state `SENDING`. If your process dies mid-call, this row is the only evidence the attempt existed.
2. **Send your own `X-Request-Id`** and store it on that row. It is echoed on every response and written to the service's audit log, so it is the key your operator can search on.
3. **On a timeout or `500`, do not retry.** Mark the row `UNKNOWN`.
4. **Poll `/transactions` with backoff** for several minutes, and watch `/balance` for a matching drop.
5. **If it still hasn't appeared, leave it `UNKNOWN` and escalate to a human**, quoting the `requestId`. Do not let an automated job resolve `UNKNOWN` into "retry."

Only ever auto-retry the failures in the "No" rows of the table above.

If exactly-once transfers matter to you, say so — an idempotency key on `/send` is a server-side change, and the service already carries `X-Request-Id` end to end, so it is a small one. Hand-rolling exactly-once semantics on the client is not a good place to be.

## Chain outages

Chains fail independently. A Bitcoin node being down does not affect an Ethereum balance check.

* A node or RPC provider that is **down, erroring or timing out** surfaces as a **`500`**. Reads are safe to retry with backoff.
* A **`503`** is *not* an outage. It means `Chain is not configured: <CHAIN>` — this deployment does not run that chain at all. It is permanent, and retrying will never help.
* `/info` degrades rather than failing: if history lookup breaks, it still returns the address and balance, with `transactions: []`. An empty list there means "we couldn't fetch history", not necessarily "no history".