# TypeScript SDK

`@tronchartsxyz/api-client` wraps the REST surface in typed resources and ships a
WebSocket client for `/ws/risk`.

## Install

```bash
npm install @tronchartsxyz/api-client
```

Ships as ESM with bundled type declarations; Node 20 or newer. The package has no
runtime dependencies.

The REST surface and the [OpenAPI spec](https://docs.troncharts.xyz/docs/reference/specs/) remain the stable
contract — the SDK is a convenience over them, not a replacement, and anything it
does not cover you can call directly.

## Instantiate

```ts
import { TronCharts } from '@tronchartsxyz/api-client'

const sdk = new TronCharts({
  baseUrl: 'https://api.troncharts.xyz',
  token: process.env.TRON_CHARTS_TOKEN!,
  tenantSlug: 'acme',   // required unless you call from a registered origin — see Tenancy
})
```

`tenantSlug` is optional in the type signature only. The SDK sends it as
`X-Tenant-Slug` when present, and every `/api/v1/*` call needs either that
header or an origin registered on your tenant — otherwise the request 404s with
`unknown_tenant_origin` before your token is read. See
[Tenancy](https://docs.troncharts.xyz/docs/auth/tenancy/).

Mint the token first. There is no unauthenticated mode — `token` is required by
the constructor — but `auth.apiToken` authenticates from the body, so the
minting client can pass an empty string:

```ts
const minter = new TronCharts({ baseUrl: 'https://api.troncharts.xyz', token: '' })
const { token } = await minter.auth.apiToken({ apiKey, apiSecret })
```

The mint path is one of the few exempt from the tenant gate, so the minting
client needs no `tenantSlug` either. Every client you build *after* it does.

## Resources

| Resource | Covers |
| --- | --- |
| `sdk.auth` | Bootstrap tokens, bearer minting. |
| `sdk.accounts` | State, positions, orders, trades, lifecycle. |
| `sdk.oms` | Compose, cancel, modify, brackets. |
| `sdk.market` | Quotes, depth (flat + grouped), FX. |
| `sdk.reports` | Historical reconciliation reads. |
| `sdk.sor` | Routing decisions and savings summary. |
| `sdk.venues` | Venue capability discovery. |
| `sdk.propAccounts`, `sdk.propTemplates`, `sdk.riskProfiles`, `sdk.tradingGroups` | The prop surface. |
| `sdk.firms` | Firm lifecycle — create, readiness, deploy, status, payouts. |
| `sdk.tenant` | Your tenant's config. |
| `sdk.kyc`, `sdk.referrals`, `sdk.analytics`, `sdk.indicators`, `sdk.backtests`, `sdk.copyTrading`, `sdk.sandbox` | The rest of the surface. |

`sdk.client` is the escape hatch — a raw typed request method for anything the
resources don't cover yet.

## End to end

```ts
const sdk = new TronCharts({ baseUrl, token, tenantSlug })

// Rehearse against a sandbox account first.
const { accountId } = await sdk.sandbox.paperAccount()

const { intentId } = await sdk.oms.composeIntent(
  { venue: 'hyperliquid', symbol: 'BTC.HL', side: 'buy', type: 'limit', qty: '0.01', price: '60000' },
  crypto.randomUUID(),   // idempotency key
)

await sdk.oms.attachBrackets({ parentIntentId: intentId, tpPrice: 64000, slPrice: 58000 })

const state = await sdk.accounts.state(accountId)
```

## WebSocket client

The socket does not take your REST bearer. Mint a single-use handshake token
with `auth.bootstrap()` and pass that:

```ts
import { RiskEngineClient } from '@tronchartsxyz/api-client'

const { data } = await sdk.auth.bootstrap()

const ws = new RiskEngineClient({
  url: data.riskEngineWss.endpoint,
  token: data.riskEngineWss.token,
  onFrame: (frame) => {
    if (frame.type === 'Position-Update') applyPosition(frame)
  },
})

await ws.connect()
ws.send({ type: 'Subscribe', topics: ['Position-Changed', 'Open-Orders'] })
```

`onFrame` is required — there is no `.on()` event API; every server frame goes
through that one handler. `connect()` sends `Authenticate` and nothing else, so
subscribe explicitly for the topics you want. The handshake token is single-use
and expires in 60 seconds, so each connect needs a fresh `bootstrap()`.

The WS client speaks JSON only. If you want the
[msgpack encoding](https://docs.troncharts.xyz/docs/realtime/connecting/#binary-encoding), drive the socket
yourself.

## Honest coverage note

The typed resources track the REST surface but are not a complete mirror of it
— market candles and the trade tape, for one, are REST-only today. When
something is missing, `sdk.client.request()` reaches it without waiting for an
SDK release. The [REST reference](https://docs.troncharts.xyz/docs/reference/rest/) renders `openapi.yaml`,
which is hand-maintained and does not cover every `/api/v1` route either — see
[Raw specs](https://docs.troncharts.xyz/docs/reference/specs/).