Authentication
The two secrets
Almost every integration bug in this area comes from conflating these. They are unrelated.
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.
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
expiresAtand reuse it. Do not call/authbefore 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 also401s, 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.
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
userIdin 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
userIdis 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.