Authentication

Two different secrets. Know which is which before you write any code.
View as MarkdownOpen in Claude

The two secrets

Almost every integration bug in this area comes from conflating these. They are unrelated.

API credentialsWallet password
Whatclient id + api keyThe password you chose at /create
Identifiesyour applicationone user’s keystore
Used by/auth, to mint a bearer token/send and /extend, to decrypt the keys
Scopethe whole servicea single userId
RecoverableNo — re-issued by the operatorNo. Never. By anyone.
RotatableAsk your operatorNo — it is fixed for the life of the wallet

The wallet password is not a setup-time secret you can forget about. It is required on every single transfer, so your system has to store it and retrieve it per user, for as long as the wallet exists.

There is no change-password endpoint and no recovery path. If a user’s password is lost, their funds cannot be moved — not by you, and not by us. The seed phrase from /create is the only other way to reach those keys, and it is shown exactly once.

Getting a token

Your client id and api key go in headers. There is no request body.

$curl -X POST "$BASE_URL/auth" \
> -H "x-client-id: $CLIENT_ID" \
> -H "x-api-key: $API_KEY"
1{
2 "result": {
3 "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
4 "expiresAt": "2026-07-13T10:15:00.000Z"
5 },
6 "requestId": "b7a1f0c2-3d4e-4a5b-9c6d-7e8f90a1b2c3"
7}

Present it on every other endpoint as Authorization: Bearer <token>. /auth is the only endpoint that does not require one.

There is no refresh token

Tokens last 15 minutes and that is the whole lifecycle. There is no refresh token, no /refresh endpoint, and no OAuth flow — when a token expires you call /auth again with the same credentials.

The practical pattern:

  • Cache the token until expiresAt and reuse it. Do not call /auth before every request; it is a signing operation, not a free lookup.
  • Re-authenticate once on a 401, then replay the original request. If the replay also 401s, stop — you have a credential problem, not an expiry problem.
  • Refresh slightly early (say, 60 seconds before expiresAt) so a request in flight doesn’t expire mid-call.

401 means two different things

This is the one status code you must disambiguate by reading message, because the correct response to each is opposite.

messageWhereWhat it meansWhat to do
Missing or expired tokenany endpointYour bearer token is absent or expired.Call /auth, retry the request. Safe and automatic.
Invalid credentials/authBad client id or api key.Stop. Your app’s credentials are wrong.
Invalid credentials/send, /extend (EVM, Tron)The wallet password is wrong.Stop. Retrying is an infinite loop.
Invalid password/send (Solana)The wallet password is wrong.Stop.

Bitcoin is the odd one out: a wrong wallet password there comes back as 400 Invalid password, not a 401.

A 401 on /send is the one failure you can trust completely. The password is checked before anything is signed, so nothing was broadcast. Once you fix the password, retrying is entirely safe — no risk of a double-send. Contrast that with a 500 or a timeout, which is genuinely ambiguous — see Reliability.

Behind a gateway: X-USER-ID

In production this service usually sits behind a trusted gateway that authenticates the end user and forwards their id in an X-USER-ID header.

When that header is present, it overrides any userId in your request body. The per-user scope comes from the authenticated session, not from free-form input — which is the point, but it will surprise you if you don’t know:

  • Calling through the gateway, the userId in your body is ignored. The header wins.
  • Calling the service directly (local testing, a server-to-server integration with no gateway), there is no header, so the body userId is used — which is why every example in these docs puts it there.

If you are behind a gateway and your requests seem to operate on the wrong user, this is why.

Rate limits

There are none. The service does not rate-limit /auth, /create or /send, and it will never return a 429 — abuse control belongs to the gateway in front of it.

That is not permission to hammer it. Because there is no push mechanism (see Reliability), a naive integration polls every user against every asset on a tight loop and generates enormous load for no benefit. Throttle yourself: poll only the assets you actually support, and back off when nothing is changing.

Credential issuance

Your operator issues the client id and api key, and shows them once — only a SHA-256 hash of the key is stored, so it cannot be recovered or re-displayed. Lost credentials are re-issued, not recovered. There is no self-service rotation endpoint; ask your operator.