Flows
API keys
Per-trader credentials for programmatic trading. A key authenticates a bot against the /v2/trading surface on its own — the trader never holds your app's OAuth client secret.
How a key authenticates
Send
X-Api-Key: <key_id>.<key_secret> (the mint response returns this composite string as api_key). No bearer token and no session token are needed alongside it. Keys work only on the trading surface — /v2/trading writes, reads and the SSE stream. Everything else — auth, payments, payouts, Connect, KYC, webhooks, notifications, and these key-management endpoints themselves — rejects a key with 401, so a leaked key cannot mint more keys or touch anything tenant-level.Scopes and revocation
A key's effective scopes are your app's scopes intersected with
reads + trading — it never inherits the api superscope, and payout initiation is unreachable. Revocation (and app deactivation) takes effect on the key's next request. A wrong or unknown credential returns 401 V2_API_KEY_INVALID; a revoked one returns 401 V2_API_KEY_REVOKED. last_used_at records the last successful use, stamped at most once a minute — the SSE stream is the one exception: it never stamps, so a stream-only bot can show last_used_at: null.Create a key
POST
/v2/api-keysUser session
Request body
label | string | required | Human-readable name |
prop_account_id | string | optional | Bind the key to one prop account you own (404 otherwise). A bound key trades and reads that account only |
200 OK — secret shown once
{
"id": "key_...",
"label": "Trading bot",
"key_id": "hskk_A1b2C3d4",
"key_secret": "opaque-random-string // shown ONCE — store it now",
"api_key": "hskk_A1b2C3d4.opaque-random-string // send as X-Api-Key",
"prop_account_id": "prop_..."
}The secret is shown once
Capture
key_secret at creation time. It is hashed at rest and can never be retrieved again — rotate by revoking and re-creating.Use the key
The bot calls the trading surface directly — no bearer, no session.
curl
# Submit an order (trade_pair is the wire id — BTCUSD, not BTC/USD)
curl -X POST http://localhost:8000/v2/trading/orders \
-H "X-Api-Key: hskk_A1b2C3d4.opaque-random-string" \
-H "Content-Type: application/json" \
-d '{ "trade_pair": "BTCUSD", "order_type": "LONG", "leverage": 0.1 }'
# Read the desk
curl http://localhost:8000/v2/trading/desk-poll \
-H "X-Api-Key: hskk_A1b2C3d4.opaque-random-string"Prop-account binding
An unbound key resolves accounts like a session does:
X-Prop-Account selects one, otherwise the most-recent is used. A key minted with prop_account_id is pinned — an omitted header resolves to the bound account (not the most-recent), the header may restate it, and naming any other account is refused with 403 V2_API_KEY_ACCOUNT_MISMATCH rather than silently redirected.List keys
GET
/v2/api-keysUser session
200 OK
[
{ "id": "key_...", "label": "Trading bot", "key_id": "hskk_A1b2C3d4", "revoked_at": null }
]GET
/v2/api-keysRuns live against your environment using the app's server-side credentials and your session. Sign in to the dashboard first for authenticated reads.
Revoke a key
DELETE
/v2/api-keys/{id}User session
curl
curl -X DELETE http://localhost:8000/v2/api-keys/key_... \
-H "Authorization: Bearer <app_access_token>" \
-H "X-Session-Token: <user_session_token>"200 OK
{ "revoked": true }