Generate an Ed25519 key pair
The public key becomes yourapiKey.
Register the key (REST)
The registration request must be ECDSA-signed by the Ethereum address that owns the key.signature is the (r, s, v) triple from the wallet’s personal_sign over the canonical message — see the POST /v1/createApiKey endpoint reference for the full signing recipe.
createApiKey is REST-only. The WebSocket server returns 501 NotImplemented for it.
Sign protected requests
Every mutating request is authenticated with your Ed25519 API key. You always send the same three values — only what you put in the signature changes per operation:| Value | REST | WebSocket |
|---|---|---|
| API key (the public key) | X-API-Key header | apiKey envelope field |
| Unix-nanosecond timestamp | X-Timestamp header | timestamp envelope field |
| Ed25519 signature (128 hex chars) | X-Signature header | signature envelope field |
| Operation | Scheme |
|---|---|
placeOrder, cancelOrder, modifyOrder | Scheme 1 — typed payload |
batchPlaceOrders, batchCancelOrders, batchModifyOrders | Scheme 1, applied once per element (see Batches) |
cancelAllOrders, setLeverage, WebSocket authenticate | Scheme 2 — legacy message |
Scheme 1 — typed payload (orders)
ForplaceOrder, cancelOrder, and modifyOrder, the signed message is the request payload itself — a compact, key-sorted JSON object built from engine-native integer values. There is no timestamp or action prefix:
ct (it must equal the X-Timestamp you send). Keys are in fixed alphabetical order with no whitespace; the address (ad) and client id (c) are lowercased.
| Operation | op | Canonical payload |
|---|---|---|
placeOrder | 1 | {"ad":"0x…","ai":N,"c":"…","ct":N,"g":N,"m":N,"op":1,"p":N,"q":N,"r":0,"s":N,"t":N,"v":1} |
cancelOrder | 2 | {"ad":"0x…","ai":N,"c":"…","ct":N,"id":"…","m":N,"op":2,"v":1} |
modifyOrder | 3 | {"ad":"0x…","ai":N,"c":"…","ct":N,"g":N,"id":"…","m":N,"op":3,"p":N,"q":N,"r":0,"s":N,"t":N,"v":1} |
ad— master Ethereum address, lowercase0x-hexai— account (subaccount) indexc— client id; omitted entirely when emptyct— client timestamp in Unix nanoseconds; equals theX-Timestampyou sendg—goodTilTimein nanoseconds (0= no expiry) —placeOrderandmodifyOrderid— serverorderId. Required formodifyOrder; forcancelOrderit is omitted when canceling by client idm— market idop— operation:1place,2cancel,3modify (4= untriggered TPSL — same fields as place)p— price in integer ticks =price ÷ market tickSizeq— quantity in integer quantums =size ÷ market stepSizer— reduce-only,0or1(integer, not a boolean) —placeOrderandmodifyOrders— side:0buy,1sell —placeOrderandmodifyOrdert— time-in-force:0GTT,1FOK,2IOC,3ALO —placeOrderandmodifyOrderv— payload version (currently1)
cancelOrder, provide exactly one of id (server order id) or c (client id). For modifyOrder, id is always required; include c only if the resting order was placed with a client id (it echoes that original value, and the engine rejects a mismatch). The g, r, s, and t fields in a modifyOrder echo the resting order’s immutable attributes so the validator can verify the signature without fetching the original — passing a new g performs a cancel-replace with a fresh expiry. Because p and q are integers, convert the human-readable decimal price and size to ticks and quantums using the market’s tickSize and stepSize from GET /v1/markets — the conversion must be exact (price ÷ tickSize with no remainder).
Resting orders (t = 0 GTT or 3 ALO) require goodTilTime / g, and it must be at least one month in the future when the API handler processes the order — nearer or missing values are rejected. 1 FOK and 2 IOC never rest, so they use g = 0.
Batches
A batch (batchPlaceOrders / batchCancelOrders / batchModifyOrders) is just Scheme 1 repeated: sign each element as its own typed payload, with every element sharing the one X-Timestamp you send as its ct. Each element carries its own signature field, and the shared X-API-Key + X-Timestamp authenticate the request. On REST the X-Signature header must still be present — set it to any one element’s signature; the batch is verified per element, not against this header (omitting it rejects every element with invalid order signature). A top-level grouping (TPSL) field is not signed.
Scheme 2 — legacy message (everything else)
cancelAllOrders, setLeverage, and WebSocket authenticate sign the timestamp, then the action, then the canonical JSON body — concatenated with no delimiters:
action is the camelCase final path segment (/v1/cancelAllOrders → cancelAllOrders); over WebSocket it’s the request type. The HTTP method is not part of the message. canonical_json serializes the body with sorted keys and no whitespace.
You do not need to call authenticate first for signed requests.
Your testnet account
A new account starts empty.POST /v1/createApiKey registers your key but credits no balance, so GET /v1/account returns 404 "this account has no activity yet" until your first deposit. (Testnet previously auto-credited $5,000 of paper-trading collateral; that has been removed.)
Most testnet users never fund manually: in the Arcus testnet web app, connect your wallet and click Testnet Deposit to instantly credit ~$1,000 USDC of paper-trading collateral. If you trade via the API and need a larger balance — or want to fund programmatically — deposit collateral on-chain instead; see Fund a testnet account.
Once funded, the deposit appears as a DEPOSIT entry in GET /v1/accountTransferUpdates and your balance shows up on GET /v1/account:
GET /v1/markets; subscribe to the markets WebSocket channel for live updates. The universe is the same for every API key.