Reliability

There are no webhooks, there is no pagination, and there is no idempotency key. Design for that.
View as MarkdownOpen in Claude

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:

1{ "result": { "hash": "0x…", "type": "send", "amount": "25.5",
2 "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:

ChainPoll everyGive up after
Solana~2s~2 min
BNB, Polygon~5s~10 min
Ethereum~15s~30 min
Tron~5s~10 min
Bitcoin~60shours — 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:

OutcomeWas anything broadcast?Safe to retry?
400 bad inputNoYes, once fixed
401 wrong passwordNoYes, once fixed
402 insufficient fundsNoYes, once funded
422 amount below the feeNoYes, with a larger amount
502 broadcast rejectedNo — the chain refused itOnly after reading the message
503 chain not configuredNoNever — permanent
500, a timeout, a dropped connectionUnknownNo — 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”.