platform, it carries a developer_key_id claim, and /dapi/v1 refuses anything else before routing —
401 unauthenticated with no bearer, 403 forbidden with details.reason: "developerSessionRequired" for an ordinary
token or a refresh token on a business route. The refresh token, audience platform-refresh, is taken by
POST /auth/refresh alone. An API key belongs to remote transfers and cannot
authenticate here at all.
This page is the reference summary. Sessions is the same flow with runnable
Python, Node and Go.
Four credentials
A key is created in the HEVN app, once per environment, not over the API — the browser generates the private half and
shows it once, and the public half joins your account’s signer in the same action. Creation fixes the IP
allowlist, which cannot be edited afterwards, so rotation is create-then-delete rather than edit.
Everything after that is code — see Developer key.
Every
/dapi/v1 request re-reads the key: it must still exist, and the call must come from an address the allowlist
covers. Deleting a key stops the sessions it already minted on their
next request, and the same checks run again on POST /auth/refresh and immediately before HEVN co-signs.
What a session may sign
Every operation is open to any platform session. What the developer key may sign is fixed by whose wallet it is: a payout or swap from your own account and an operator action on your escrow deals, and nothing of a client’s. A client’s payout answersauthorization: "client" and is approved by its holder in the sign-in window that
POST /connect/sessions opens; confirming it with your key is 403 account_not_controlled. See
Sign in with HEVN.
Log in
Two calls, neither of which needs a token.1
Start a challenge
POST /auth/challenge with {email, publicKey, nonce, requestExpiry, signature}, where
signature is your key’s signature over these exact UTF-8 bytes:nonce is a fresh non-negative integer and requestExpiry is epoch milliseconds at most
five minutes ahead. The answer is a challengeId and a base64 payload.2
Exchange the signed payload for a session
Base64-decode
payload, sign those exact bytes, and call POST /auth/token with
{challengeId, signature}. It answers accessToken, refreshToken, expiresIn and userId.401 invalid_credentials, so probing learns nothing; only an expiry outside the window differs, as
400 request_expired.
POST /auth/token also takes the challenge-free form directly: the same {email, publicKey, nonce, requestExpiry, signature} body POST /auth/challenge takes, signed over the same proof string, in one call instead of two. The
two-call exchange above is the documented path because it binds the session to a payload HEVN chose.
Re-mint an access token
POST /auth/refresh also accepts {"userId": "cl_…"} and mints a token scoped to that one client.
It still works and it is no longer the documented path: it costs one mint per client per hour and
buys nothing X-Hevn-Account does not give you.
Act as a client
Send the client’s id alongside your own access token:/client, /client/balance, /client/kyb and /client/kyb/complete require it, POST /clients/link,
GET /clients, POST /connect/sessions and every escrow route refuse it, and everything else takes it or leaves it. The table of which is which is in
Sessions.
Sending the header where it is refused is an error, not a silent no-op, and so is omitting it where it is required:
400 account_header_invalid, 400 account_scope_conflict, 404 account_not_found, 403 account_forbidden and
422 validation_failed on the missing header are the five you will meet.
Signing money movement
A token is enough to read data, write metadata and book a client’s payout. Moving money out of your own account additionally needs a signature from the developer key, because that key — not HEVN alone — can authorize a spend from your wallet.1
Prepare
The opening call —
POST /payouts, POST /swaps, POST /escrow/{escrowId}/actions —
prices the payment, books it, builds the on-chain operation and answers approval.payload
plus a debit decoded from that operation.2
Check and sign
Compare
debit with the payment your own system intended. Then base64-decode payload and sign
those exact bytes: ECDSA P-256 over SHA-256, DER-encoded, standard padded base64. Raw
r‖s, base64url and a re-serialized JSON body all produce 400 signature_invalid.3
Confirm
Post
{signature} to the matching confirm route. HEVN verifies it locally, co-signs and submits.
A 202 means the receipt has not landed — confirm again with the same body; never re-open.403. The byte-level rules, the pitfalls and the retry table are in Signing.
Idempotency
Idempotency-Key is accepted on every write and required on none. It matches
^[A-Za-z0-9._:-]{1,128}$, is scoped to your account and the operation, and replays the first
answer with Idempotency-Replayed: true. Omit it and the server derives one and echoes it.
Rate limits
Rate limits are per credential: 120 writes and 600 reads a minute, with their own per-IP, per-email and per-challenge windows on login. Over one is429 rate_limited with Retry-After. Every number
is in Limits.