Skip to content

Quickstart

From a fresh credential to a page of data and a live stream. Everything here runs server-side, which in early access is where every part of this API works.

Before you start

You need a client_id and client_secret from your administrator, and the base URL issued with them. See Authentication for how the credential is created and what it carries.

1. Mint a token

export BASE=https://platform-api.example.com

TOKEN=$(curl -sS -X POST "$BASE/api/v1/oauth/token" \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'scope=accounts:read' \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')

The token lives 10 minutes. Cache it — the token endpoint allows 10 requests a minute, so minting per call will fail. If this step returns {"error":"invalid_client"}, the credential, the secret, or the address you are calling from is wrong; the API deliberately does not say which.

2. Read something

The account status endpoint is the quickest end-to-end check, because a result proves your credential, your network path, and your scopes are all correct at once.

curl -sS -X POST "$BASE/api/v1/accounts/status/query" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"limit": 5}'
{
  "accounts": [
    {
      "account_id": 4210001,
      "trading_account_id": 4210044,
      "currency": "USD",
      "balance": "10000.00",
      "equity": "9874.60",
      "margin_level": "493.73",
      "live": true
    }
  ],
  "next_cursor": "eyJzb3J0Ijo..."
}

Note the X-Request-Id on the response. Log it on every call, successful or not — it is what identifies the exact request in a support conversation.

3. Page through everything

Ask for the next page with the cursor you were given, and stop when there is no cursor:

curl -sS -X POST "$BASE/api/v1/accounts/status/query" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"limit": 500, "after": "eyJzb3J0Ijo..."}'

There is no page number and no offset. See paging for why that is the right shape for a job running against a live book.

4. Query with a filter

curl -sS -X POST "$BASE/api/v1/monetary_transaction/query" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "filter": {"kind": "condition", "field": "method", "op": "eq", "values": ["Deposit"]},
        "sort": "created_at",
        "limit": 100
      }'

This one needs transactions:read, so add it to the scope you request in step 1. If the scope is missing you get 403 forbidden — asking for a scope your credential was never granted fails earlier, at the token endpoint, with invalid_scope.

See bulk reads for the filter shape and the entities you can query.

5. Make your first write

Creating an account is the smallest complete write, and it exercises the two rules every other write shares: the Idempotency-Key header, and ids as strings.

curl -sS -X POST "$BASE/api/v1/accounts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{
        "client_reference_id": "ACME-8891",
        "email": "ada@example.com",
        "first_name": "Ada",
        "brand_id": "13001"
      }'

This needs accounts:write in the scope you request in step 1. Send the identical request again with the same key and you get the same response back rather than a second account.

Two things to internalize before you build on this:

  • client_reference_id is permanent. It decides which account a retry lands on, so take it from your own primary key and never regenerate it.
  • Repeating a key does not check that the body still matches. Same key, same subject, different payload returns the original result and ignores what you sent.

Idempotency and retries is the page that makes both of those precise. Read it before you write a retry loop, then see writing data, trading and transactions.

6. Open the stream

const ws = new WebSocket(
  'wss://platform-api.example.com/api/v1/ws',
  ['broker-api.v1', `bearer.${token}`]
);

ws.onopen = () => {
  ws.send(JSON.stringify({ type: 'subscribe', channel: 'events' }));
};

ws.onmessage = (m) => {
  const frame = JSON.parse(m.data);
  if (frame.type === 'event') {
    handle(frame.event);
    lastSeq = frame.seq;          // remember this; it is your resume cursor
  }
};

The events channel needs stream:account. On reconnect, subscribe again with after_seq: lastSeq and you pick up exactly where you left off. See WebSocket streaming.

Checklist before you go live

  • Secret in a secret manager, and rotation rehearsed at least once.
  • Token cached and refreshed ahead of expiry, minted once across workers.
  • X-Request-Id logged on every call, success and failure.
  • 429 honors Retry-After.
  • Money parsed as decimal strings, never floating-point numbers.
  • Error handling branches on code, with a sane default for codes you do not know.
  • Stream consumers persist the last processed seq and resume with it.
  • Every write sends an Idempotency-Key derived from the operation, not from the attempt.
  • A corrected request after a 4xx uses a new key — the old one replays the old refusal.
  • A 409 idempotency_in_flight backs off for a few seconds rather than hammering.
  • A 501 is never retried, and is routed to a human.
  • Ids read from the query endpoints are converted to strings before they go into a write.