Skip to main content
Arcus is non-custodial. The server holds only the public half of your API key, and registration is gated by an ECDSA signature from the master Ethereum address that owns the key.

Generate an Ed25519 key pair

The public key becomes your apiKey.

Register the key (REST)

The registration request must be ECDSA-signed by the Ethereum address that owns the key.
The signature is the (r, s, v) triple from the wallet’s EIP-712 typed-data signature (eth_signTypedData_v4) — see the POST /v1/createApiKey endpoint reference for the domain, types, and complete signing examples. 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: There are two signing schemes. Pick by operation: Batches are not a third scheme — they are Scheme 1 signed separately for each order. Both schemes produce an Ed25519 signature; they differ only in which bytes you sign.

Scheme 1 — typed payload (orders)

For placeOrder, 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:
The timestamp lives inside the payload as 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. Fields:
  • ad — master Ethereum address, lowercase 0x-hex
  • ai — account (subaccount) index
  • c — client id; omitted entirely when empty
  • ct — client timestamp in Unix nanoseconds; equals the X-Timestamp you send
  • ggoodTilTime in nanoseconds (the request’s microsecond goodTilTime × 1000; required on every order — see below) — placeOrder and modifyOrder
  • id — server orderId. Required for modifyOrder; for cancelOrder it is omitted when canceling by client id
  • m — market id
  • op — operation: 1 place, 2 cancel, 3 modify (4 = untriggered TPSL — same fields as place)
  • p — price in integer ticks = price ÷ market tickSize
  • q — quantity in integer quantums = size ÷ market stepSize
  • r — reduce-only, 0 or 1 (integer, not a boolean) — placeOrder and modifyOrder
  • s — side: 0 buy, 1 sell — placeOrder and modifyOrder
  • t — time-in-force: 0 GTT, 1 FOK, 2 IOC, 3 ALO — placeOrder and modifyOrder
  • v — payload version (currently 1)
For 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). Every order requires goodTilTime / g — including 1 FOK and 2 IOC — and it must be at least one month in the future when the API handler processes the order; nearer or missing values are rejected. FOK and IOC orders never rest, but the field still acts as a mandatory replay-protection timestamp on them.

Batches

A batch (batchPlaceOrders / batchCancelOrders) 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/cancelAllOrderscancelAllOrders); 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:
Testnet trades against a paper-trading universe of perpetuals across crypto, equities, commodities, and indices — all quoted against USD. The full list lives at GET /v1/markets; subscribe to the markets WebSocket channel for live updates. The universe is the same for every API key.