# Create a trading group and add an account

A trading group is where an account's fees, leverage cap and venue scope come
from at compose time. When this is done you have a named group and at least one
account resolving its trading economics through it.

1. ### Create the group

   `slug` and `name` are the only required fields. The slug is kebab-case, up
   to 32 characters, and unique per tenant — a collision returns 409
   `slug_taken`.

   ```bash
   curl -s https://api.troncharts.xyz/api/v1/trading-groups \
     -H "authorization: Bearer $TOKEN" \
     -H "x-tenant-slug: $TC_TENANT_SLUG" \
     -H 'content-type: application/json' \
     -d '{"slug":"crypto-perps","name":"Crypto Perps","description":"HL + Aster desk"}'
   # → 201 { "group": { "id": "…", "slug": "crypto-perps", "isDefault": false, … } }
   ```
   ```ts
   const { group } = await sdk.tradingGroups.create({
     slug: 'crypto-perps',
     name: 'Crypto Perps',
     description: 'HL + Aster desk',
   })
   ```
2. ### Set fees, leverage and scope

   Everything economic lives under `config`. A PATCH replaces the whole object
   rather than merging, so send the full config each time.

   ```bash
   curl -s -X PATCH https://api.troncharts.xyz/api/v1/trading-groups/$GROUP_ID \
     -H "authorization: Bearer $TOKEN" \
     -H "x-tenant-slug: $TC_TENANT_SLUG" \
     -H 'content-type: application/json' \
     -d '{
       "config": {
         "fees": { "defaultMakerBps": 2, "defaultTakerBps": 5 },
         "leverage": { "defaultCap": 20 },
         "venuesAllowlist": ["hyperliquid", "aster"],
         "riskLimits": { "dailyLossCapUsd": 5000 }
       }
     }'
   ```
   ```ts
   await sdk.tradingGroups.update(group.id, {
     config: {
       fees: { defaultMakerBps: 2, defaultTakerBps: 5 },
       leverage: { defaultCap: 20 },
       venuesAllowlist: ['hyperliquid', 'aster'],
       riskLimits: { dailyLossCapUsd: 5000 },
     },
   })
   ```
   :::caution[`config` is a strict schema]
   Any key outside the catalogue — including a typo like `venueAllowlist` —
   fails the whole request with 400 `invalid_body`. The valid keys are
   `venuesAllowlist`, `symbolsAllowlist`, `fees`, `leverage`,
   `maxContractsPerOrder`, `riskLimits`, `perSymbol`, `perVenue`, `skew`,
   `bands` and `bridgeMarkupBps`.
   :::

3. ### Add an account

   Binding writes `accounts.trading_group_id`, which is what compose reads for
   fees, leverage and scope on the next order.

   ```bash
   curl -s https://api.troncharts.xyz/api/v1/trading-groups/$GROUP_ID/bind \
     -H "authorization: Bearer $TOKEN" \
     -H "x-tenant-slug: $TC_TENANT_SLUG" \
     -H 'content-type: application/json' \
     -d '{"accountId":"'"$ACCOUNT_UUID"'"}'
   # → { "ok": true, "accountId": "…", "tradingGroupId": "…" }
   ```
   ```ts
   await sdk.tradingGroups.bind(group.id, accountUuid)
   ```
   `accountId` is the account UUID. A human account number such as `100042`
   returns 404 `account_not_found`. Bind the literal id `none` to release an
   account back to the tenant default group.

4. ### Make it the tenant default

   The default group is what every unbound account inherits, including accounts
   created later. The flip is atomic within your tenant.

   ```bash
   curl -s -X POST https://api.troncharts.xyz/api/v1/trading-groups/$GROUP_ID/make-default \
     -H "authorization: Bearer $TOKEN" \
     -H "x-tenant-slug: $TC_TENANT_SLUG"
   ```
   ```ts
   await sdk.tradingGroups.makeDefault(group.id)
   ```
   :::note[Allowlists are advisory in production today]
`ENFORCE_TRADING_GROUP_ALLOWLIST` defaults `false`, so a populated
`venuesAllowlist` or `symbolsAllowlist` annotates rather than blocks an order.
Fees, leverage caps and risk limits apply regardless.
:::

**Next:** [Create a challenge template](https://docs.troncharts.xyz/docs/recipes/create-a-challenge-template/) ·
[Create a risk profile](https://docs.troncharts.xyz/docs/recipes/create-a-risk-profile/) ·
[Orders & OMS](https://docs.troncharts.xyz/docs/trading/orders/)