Skip to content

Paper accounts

POST /api/v1/sandbox/paper-account

Every field is optional — an empty body gets sensible defaults.

Terminal window
curl -s https://api.troncharts.xyz/api/v1/sandbox/paper-account \
-H "authorization: Bearer $TOKEN" \
-H "x-tenant-slug: $TC_TENANT_SLUG" \
-H 'content-type: application/json' \
-d '{
"baselineUsd": "50000",
"symbol": "BTC.HL",
"seedDemoPosition": true,
"displayName": "integration rehearsal"
}'
Field Default Notes
baselineUsd "50000" Starting balance, as a positive decimal string.
symbol "BTC.HL" The symbol for the seeded demo position.
seedDemoPosition true Opens one small position so your first read isn’t empty.
displayName A label for your own bookkeeping.

201, with a fixed shape:

{
"accountId": "",
"accountNumber": "000123",
"propTenantId": "",
"templateId": "",
"mode": "paper",
"demoPositionSeeded": true,
"ws": { "hint": "Subscribe on the risk WS …" }
}

accountId is the canonical identity — use it everywhere. accountNumber is bare zero-padded digits (a legacy prefixed form is still accepted on input, but not emitted).

Three gates, checked in this order:

  1. Deployment — off means 404, before anything else is looked at.
  2. Credential type — an API-client bearer token. A browser cookie session gets 403 api_client_required.
  3. TierfullTrading. readonly and liquidation get 403 tier_insufficient.

No prop:manage scope is needed. Other failures: 400 invalid_body with the offending fields, 402 where a card on file is required, and 422 when provisioning can’t complete.

The account is in your credential’s ownership scope from the moment it exists:

Terminal window
curl -s https://api.troncharts.xyz/api/v1/accounts \
-H "authorization: Bearer $TOKEN" \
-H "x-tenant-slug: $TC_TENANT_SLUG"
curl -s https://api.troncharts.xyz/api/v1/accounts/$ACCOUNT_ID/state \
-H "authorization: Bearer $TOKEN" \
-H "x-tenant-slug: $TC_TENANT_SLUG"

A /ws/risk socket carries one active account at a time, so after provisioning you re-subscribe with the new id explicitly:

{ "type": "Subscribe", "id": "", "topics": [""], "accountId": "<accountId>" }

The account must be one your credential owns; anything else is rejected with account_forbidden. You’ll then receive Account-State-Update, Balance-Update, and Margin-Update for it like any other account — see Risk channel.

The supporting chain behind a sandbox account is reused across calls, but the account itself is not deduplicated — every call creates a new one. That’s deliberate (a clean account per rehearsal), but it means a retry loop around this endpoint quietly accumulates accounts. Provision once per rehearsal and keep the accountId.

The sandbox runs a paper-only template, but the rules on it are enforced exactly like a real prop account — same engine, same gate, same rejection. The template carries two rules:

Rule Threshold At the 50000 default
max_drawdown_usd 10% of the baseline 5000
daily_loss_cap_usd 5% of the baseline 2500

An order that would breach either is rejected at compose with prop_breach_would_occur (HTTP 409) — the venue never sees it. And a breach is terminal: the account moves to breached, after which any new non-reduce-only order on it is refused for terminal_status. Reduce-only orders still go through, so you can close what you hold.

There is no un-breach. To keep rehearsing, provision a fresh account with another POST /api/v1/sandbox/paper-account.

This is the same enforcement described in Hard enforcement is live and default-on, so the error handling you build here carries over to a real template unchanged.