Skip to main content
A small set of session, key, and device-management endpoints skip the Base64SignedPayload envelope and authenticate with a header triple called SessionSig. You sign a canonical per-endpoint message with your Ed25519 session key and send the signature in three headers. You sign a canonical message built from the request’s fields, not the JSON body. The body is parsed separately, and signing it instead fails verification with 401. The exact byte layout is in What you sign.

When to use SessionSig

SessionSig covers API-key management and device login: the body is JSON, but the action still needs a session signature.
EndpointAction
GET /api/v1/api-keysList your API keys (prefixes only)
POST /api/v1/api-keysCreate an API key
POST /api/v1/api-keys/{id}/deleteDelete an API key
POST /api/v1/loginMint a device key (device login)
Order entry, cash movement, and account/portfolio writes use the signed-payload envelope instead. Reads of account, order, and position data use the X-API-KEY / X-DEVICE-KEY header credentials. The signing key is an Ed25519 session key. Minting an admin-scope (unpinned) API key requires an admin-rooted session.

The header triple

Send three headers. All values use standard base64 (+ / / alphabet, = padding); URL-safe base64 is rejected.
HeaderContents
X-PUBLIC-KEYBase64 of the 32-byte Ed25519 session public key the exchange verifies against
X-SIGNATUREBase64 of the 64-byte Ed25519 signature over the canonical message (the canonical bytes, not the JSON body and not the base64 text)
X-REQUEST-IDA UUIDv7. It is both the freshness check and the idempotency key; its embedded timestamp must be current (see Clock skew)

What you sign

Each endpoint defines an exact byte sequence, the canonical message. Build it from the request’s own fields, sign those bytes with the Ed25519 session key, and put the signature in X-SIGNATURE. In every message, request_id is the raw 16 bytes of the X-REQUEST-ID UUIDv7, integers are little-endian, and strings are UTF-8 with no terminator.
EndpointCanonical message
GET /api/v1/api-keysrequest_id (16) ‖ account_id (8 LE)
POST /api/v1/api-keysrequest_id (16) ‖ account_id (8 LE) ‖ subaccount_or_max (4 LE) ‖ key_name (UTF-8)
POST /api/v1/api-keys/{id}/deleterequest_id (16) ‖ account_id (8 LE) ‖ api_key_id (16 raw bytes from the URL)
POST /api/v1/loginrequest_id (16) ‖ account_id (8 LE) ‖ subaccount_or_max (4 LE) ‖ "device-login"
subaccount_or_max is the subaccount index for a pinned credential, or the maximum 32-bit value as the sentinel for an unpinned (admin-scope) credential. The trailing "device-login" on POST /api/v1/login is that literal ASCII string.
Mint the API key or device key at the scope your session can reach. A pinned session cannot mint an unpinned (account-wide) credential. See Read credentials for pinned vs unpinned scope.

Procedure

1

Generate a UUIDv7

Mint a fresh UUIDv7 at send time. Its embedded timestamp must be current when the exchange receives the request.
2

Build the canonical message

Concatenate the fields for the endpoint, in order, using the raw 16-byte request id and little-endian integers.
3

Sign with the session key

Sign the canonical bytes with your Ed25519 session private key.
4

Send the headers and the JSON body

Set X-PUBLIC-KEY, X-SIGNATURE, and X-REQUEST-ID (all standard base64), attach the JSON body, and POST.

Clock skew

The X-REQUEST-ID UUIDv7 must carry a current timestamp. The exchange rejects a request whose timestamp is too old or too far in the future, returning 400 with code request_timestamp_skew. Generate the UUIDv7 at send time and keep your clock in sync (NTP).

Idempotency

X-REQUEST-ID is also the dedup key. Reusing the same UUIDv7 returns the original result instead of acting twice. After a 500, 503, 504, or a network error, retry with the same X-REQUEST-ID; the request is deduped, not executed twice.

Read the body

These endpoints return their own JSON response, and a 200 OK is not proof of success. Always read the body, per the error model. The HTTP status codes and retry semantics live there and in error codes; the 429 / 413 limits are in rate limits.

Common mistakes

Before you ship a SessionSig client, walk the common signing mistakes checklist. The frequent SessionSig failures:
  • Signing the JSON body instead of the canonical message.
  • Using URL-safe base64 in a header. Use standard base64.
  • Using a non-UUIDv7 X-REQUEST-ID — the exchange needs the embedded timestamp.
  • Sending the wrong subaccount_or_max sentinel for the scope you want.
  • Sending a request_id whose timestamp is not current.