# Paper accounts

`POST /api/v1/sandbox/paper-account`

## Provision one

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

```bash
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. |

:::caution[Use a venue-suffixed symbol]
`BTC.HL` prices; a bare `BTC` does not. A symbol with no venue suffix has no
pricing venue, so the seeded position's mark **stays frozen** and every P&L
read off it is meaningless. Take symbols from
`GET /api/v1/symbols/{venue}`.
:::

## The response

`201`, with a fixed shape:

```json
{
  "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

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. **Tier** — `fullTrading`. `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.

## Read it

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

```bash
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

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

```json
{ "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](https://docs.troncharts.xyz/docs/realtime/risk/).

## 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

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`.

:::caution[The thresholds are fixed on your first call]
The sandbox template is created once per tenant, from the `baselineUsd` of the
**first** provisioning call, and is reused unchanged after that. A later call
with a different `baselineUsd` changes the new account's paper balance but not
the template's rule thresholds. Pick the baseline you want to rehearse at on
the first call — or deliberately breach a throwaway account, to see the `409`
your integration has to handle.
:::

This is the same enforcement described in
[Hard enforcement is live and default-on](https://docs.troncharts.xyz/docs/launch/prop-accounts/#hard-enforcement-is-live-and-default-on),
so the error handling you build here carries over to a real template unchanged.