API-key (HMAC) authentication
Send three headers on every private request:The canonical message
Because the method, path, and body are covered, one signature authorises exactly
one request. It cannot be replayed against a different path, a different
limit=, or different body contents.
The exact string signed, for a POST
Sign the body you actually send. If your HTTP client re-serialises JSON — reordering keys or changing whitespace — after you compute the signature, the signature will not match. Build the body string once, sign that string, and transmit it verbatim.
Clock skew
X-Auth-Nonce is checked against server time. Read
GET /api/v2/atlas/public/timestamp (Unix milliseconds) to measure your
offset before signing.
WebSocket
The private WebSocket upgrade is signed exactly like a REST call, so the signedpath must include the full ?stream=… query string you connect with.
Signing the bare path will fail.
Authentication is not authorisation to trade
Signing the request proves who you are. It does not authorise an order — every non-custodial order carries your own EIP-712 signature as well, and whether your client can produce one depends entirely on what owns your Safe. Checksignature_type on GET /api/v2/atlas/account/wallet before you build.
The number tells you what owns your Safe, which is what decides how your client
signs — and whether it can sign at all:
Open your type below.
- 3 — passkey wallet
- 2 — your own wallet
- Custodial
Reads work as normal. For writes it depends on whether you have added a
second signing key.A passkey signs only in the browser it was registered in, and session keys are
generated non-extractable, so neither can reach a server. Without a second key
your client can read balances, positions, orders, contracts and market data,
and nothing else.With a second signing key (Settings →
Wallet → Signing keys — adding one requires two-factor authentication) your
client signs as an owner of your Safe: orders, withdrawals and redemptions
all work.
The order stays Sign the SafeMessage, not the order hash — the Safe hashes it again itself, so
signing the order hash directly always fails validation. The symptom is a
The domain is
signature_type: 3 with maker == signer == your Safe — what
changes is the signature, which becomes a standard Safe owner signature:422 reading signed_order signature was not accepted by the maker wallet,
on a signature that recovers to your key perfectly when you check it
yourself — the Safe is recovering against a different digest.Computed OFFLINE. Your Safe exposes getMessageHash(bytes) and will return
the same value, but that needs an RPC connection to the chain, which an API
client generally has no reason to hold:chainId and verifyingContract only — no name, no
version. That is Safe 1.3.0’s domain, and getting it wrong fails exactly
the same way as signing the raw order hash, so verify once against
getMessageHash if you can reach a node.Related
API overview
Base URLs, response format, and pagination.
Errors
The dot-coded error catalog.
Signed orders
The EIP-712 signature every order carries, separate from authentication.
Session keys
Browser-held keys that sign orders without a passkey prompt.