Authentication
All customer-facing /v1/* endpoints authenticate with a bearer
token:
Authorization: Bearer <prefix>.<suffix>
Two flows produce bearer tokens, and the venue treats them uniformly:
- API keys — minted via
POST /v1/credentials, intended for programmatic trading. - Sessions — produced by
POST /v1/auth/login(email + password), used by the terminal. Sessions are credential rows with a 7-day expiry;POST /v1/auth/logoutrevokes the presented one.
Public market data (instruments, books, trades, candles) and the WebSocket upgrade itself require no auth.
Token format
The token's prefix is the credential's public ID, used for lookup;
the suffix is a high-entropy secret. The venue stores only a SHA-256
of the suffix.
The venue returns a bearer token exactly once, at creation:
POST /v1/auth/signup→ session token, plus the accountPOST /v1/credentials→data.plaintext_key
GET /v1/credentials never returns secrets. If you lose a key, mint a
new credential and DELETE the old one.
Entitlements
Each credential carries an entitlement bitmask. The default for an
account's initial credential is 15 (all four bits):
| Bit | Value | Entitlement | Grants |
|---|---|---|---|
| 0 | 1 | TradeEnabled | Place and cancel orders |
| 1 | 2 | WithdrawEnabled | Initiate withdrawals |
| 2 | 4 | DepositEnabled | Allocate deposit addresses |
| 3 | 8 | APITrading | Programmatic (vs UI-only) trading |
Scoped agent credentials
Since API 0.14.0 a credential can carry a scope the venue enforces on
every order before it reaches the matching engine — the ring-fence for an
AI agent or a bot, independent of the account's own limits. Set it at mint
time on POST /v1/credentials; it is echoed as
scope on every credential and, with today's usage, on
GET /v1/account/limits.
| Field | Effect |
|---|---|
instrument_allowlist | Orders on any other instrument fail 403 instrument_not_allowed. Cancels are never restricted. |
max_order_notional_uusdc | Per-order cap: 403 order_notional_cap. Limit orders count price × tick value × count; market buys count max_cost. |
max_daily_notional_uusdc | Accepted notional per UTC day, per credential, durable across restarts: 403 daily_notional_cap. |
max_open_orders | Refuses new orders while the account has this many resting: 403 open_orders_cap. |
approval_mode | require_human parks every order (202 pending_approval) for a human to approve within 15 minutes — see MCP order flow. |
agent | Free-form descriptor (name, model, operator, …). name is stamped on the credential's fills. |
expires_in_s | Absolute expiry; an expired key fails 401. |
A typical agent key: trade entitlement only (entitlement_mask: 9), a
week's expiry, the two instruments it is allowed to touch, a per-order and
a daily cap, and require_human until you trust it. Revoke it with
DELETE /v1/credentials/{credential_id} when the
run ends. POST /v1/orders/stage previews the scope's verdict
(would_reject, scope) before an order is committed.
The terminal's Account page has an Agents card that mints exactly this
kind of key from a form (trade + API entitlements only, require_human
preselected), shows every key's scope and today's notional against its
daily cap (daily on GET /v1/credentials, API 0.16.0), and carries
the approval queue and an emergency stop.
Account states
Independently of credentials, every account has a lifecycle state. Endpoints check both the credential's entitlements and the account state:
| Account state | Place order | Cancel order | Withdraw |
|---|---|---|---|
pending | — | — | — |
active | ✓ | ✓ | ✓ |
trade_frozen | — | ✓ | ✓ |
withdraw_frozen | ✓ | ✓ | — |
fully_frozen | — | ✓ | — |
closed | — | — | — |
Cancellation stays allowed in frozen states so you can manage positions
down; a closed account is inert.
State-related rejections use the account_not_active and
account_trade_frozen error codes.
WebSocket auth
Private channels (fill, order, account) unlock by presenting the same
bearer on the upgrade request to GET /v1/ws. Public channels need
no auth. Tokens never appear in URLs. See the
streaming overview.