Skip to main content
Every authenticated operation in this reference takes one header:
The access token is a short-lived JWT your backend mints for itself, and the only credential this API accepts. Its audience is 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 answers authorization: "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.
A challenge is single-use and expires within minutes. Every credential problem — unknown email, a key that is not on the account, a detached signer, a bad signature, a source IP the allowlist does not cover — answers the same 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

Log in once per process, keep the refresh token in your secret store, and re-mint about a minute before the access token expires. 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:
Omit the header and most calls act on your integrator account. One token covers every client linked to you, so there is nothing to cache per client. No operation names a client in its path any more, so the header is the only selector: /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.
Tokens are bound to the server that issued them. A sandbox token is refused by production and the reverse, so log in separately against each host, and keep one developer key — created in that environment’s app, with that environment’s egress IPs — per environment.

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.
Approvals are short: 120 seconds for a payout, 60 seconds for an escrow action. HEVN re-checks the key and your source IP immediately before it co-signs, so a key deleted between prepare and confirm fails the confirm with a 403. The byte-level rules, the pitfalls and the retry table are in Signing.
The developer key can authorize a spend from your own wallet and act on every client linked to you. Keep the private half in a secret manager, never in a browser, and rehearse rotation before you need it.

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 is 429 rate_limited with Retry-After. Every number is in Limits.