Paper accounts
POST /api/v1/sandbox/paper-account
Provision one
Section titled “Provision one”Every field is optional — an empty body gets sensible defaults.
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. |
The response
Section titled “The response”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).
Who can call it
Section titled “Who can call it”Three gates, checked in this order:
- Deployment — off means
404, before anything else is looked at. - Credential type — an API-client bearer token. A browser cookie
session gets
403 api_client_required. - Tier —
fullTrading.readonlyandliquidationget403 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.
Read it
Section titled “Read it”The account is in your credential’s ownership scope from the moment it exists:
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"Stream it
Section titled “Stream it”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.
Calling it more than once
Section titled “Calling it more than once”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.
What the sandbox enforces
Section titled “What the sandbox enforces”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.