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_idis 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-Idlogged on every call, success and failure. -
429honorsRetry-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
seqand resume with it. - Every write sends an
Idempotency-Keyderived from the operation, not from the attempt. - A corrected request after a
4xxuses a new key — the old one replays the old refusal. - A
409 idempotency_in_flightbacks off for a few seconds rather than hammering. - A
501is never retried, and is routed to a human. - Ids read from the query endpoints are converted to strings before they go into a write.