You're on the production developer portal. The interactive demo — signup, Sumsub KYC, the purchase flow, webhooks, and the trading dashboard — is disabled here because it would create real accounts. Test those flows in the sandbox against the staging API.

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-keys
User session
Request body
labelstring
required
Human-readable name
prop_account_idstringoptionalBind 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-keys
User session
200 OK
[
  { "id": "key_...", "label": "Trading bot", "key_id": "hskk_A1b2C3d4", "revoked_at": null }
]
GET
/v2/api-keys Try it in the sandbox

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 }