# Create a prop firm challenge template

A template is what a trader actually buys: an account size, an entry fee, a
payout split, and the rule set the engine judges the run against. When this is
done you have an `active` template that can be sold as a
[paper prop account](https://docs.troncharts.xyz/docs/recipes/sell-a-paper-challenge/) immediately.

1. ### Collect the firm and trading group ids

   A template belongs to one firm (`propTenantId`) and allocates to one trading
   group (`tradingGroupId`). Both are required on create.

   ```bash
   curl -s https://api.troncharts.xyz/api/v1/trading-groups \
     -H "authorization: Bearer $TOKEN" \
     -H "x-tenant-slug: $TC_TENANT_SLUG"
   # → { "groups": [ { "id": "…", "slug": "crypto-perps", "isDefault": true, … } ] }
   ```
   ```ts
   const { groups } = await sdk.tradingGroups.list()
   const { firms } = await sdk.firms.list()   // needs firm:operate
   ```
   :::note[Two different scopes]
   `GET /api/v1/firms` needs `firm:operate`, while everything else here needs
   `prop:manage`. With only `prop:manage`, read `propTenantId` off any row from
   `GET /api/v1/prop-templates` — creating a firm seeds starter templates.
   :::

2. ### Write the rule set

   `rules` is a discriminated union keyed on `type`, one to 32 entries of
   `{ type, value }`. An unknown `type` fails the body parse with a 400
   `invalid_body`, so pick from the catalog rather than inventing a field.

   ```json
   [
     { "type": "max_drawdown_usd",   "value": 1250 },
     { "type": "profit_target_usd",  "value": 2000 },
     { "type": "min_trading_days",   "value": 3 }
   ]
   ```

   `_usd` rules take a plain number in the template's currency. Every `_pct`
   rule takes a 0..1 fraction — `max_concentration_pct: 0.3` is 30%.

   The template is the only place you write these. The server materializes a
   **managed** risk profile holding them and binds it, so you never create or
   name a profile for a single program — see
   [How an account is configured](https://docs.troncharts.xyz/docs/launch/account-configuration/).

3. ### Create the template

   `id` is yours to choose and must be lower-kebab-case. `accountSizeUsd`,
   `feeUsd` and `payoutSplitPct` are decimal **strings**, not numbers.

   ```bash
   curl -s https://api.troncharts.xyz/api/v1/prop-templates \
     -H "authorization: Bearer $TOKEN" \
     -H "x-tenant-slug: $TC_TENANT_SLUG" \
     -H 'content-type: application/json' \
     -d '{
       "id": "acme-25k-two-step",
       "name": "Acme 25K Evaluation",
       "accountSizeUsd": "25000.00",
       "feeUsd": "199.00",
       "payoutSplitPct": "80",
       "durationDays": 30,
       "propTenantId": "'"$FIRM_ID"'",
       "tradingGroupId": "'"$GROUP_ID"'",
       "rules": [
         { "type": "max_drawdown_usd",  "value": 1250 },
         { "type": "profit_target_usd", "value": 2000 }
       ]
     }'
   # → 201 { "template": { "id": "acme-25k-two-step", "active": true, … } }
   ```
   ```ts
   const { template } = await sdk.propTemplates.create({
     id: 'acme-25k-two-step',
     name: 'Acme 25K Evaluation',
     accountSizeUsd: '25000.00',
     feeUsd: '199.00',
     payoutSplitPct: '80',
     propTenantId: firmId,
     tradingGroupId: groupId,
     rules: [
       { type: 'max_drawdown_usd', value: 1250 },
       { type: 'profit_target_usd', value: 2000 },
     ],
   })
   ```
   :::caution[`propTenantId` is optional in the schema, required in practice]
   A provider credential is never super-scoped, so omitting `propTenantId`
   returns 403 `forbidden`. Omitting `tradingGroupId` returns 400
   `trading_group_required`.
   :::

4. ### Confirm it is sellable

   The response carries `active: true`. Re-read the catalogue to see it beside
   the rest, then hand `id` and `propTenantId` to `create-prop`.

   ```bash
   curl -s "https://api.troncharts.xyz/api/v1/prop-templates?activeOnly=true" \
     -H "authorization: Bearer $TOKEN" \
     -H "x-tenant-slug: $TC_TENANT_SLUG"
   ```
   ```ts
   const { templates } = await sdk.propTemplates.list({ activeOnly: true })
   ```
   ## Sharing one rule set across templates

Instead of `rules`, send `riskProfileId` to point several templates at one
authored profile — useful when three account sizes share the same limits. The
profile's rules are copied down into the template, so the snapshot each account
freezes still matches exactly.

```ts
const { profile } = await sdk.riskProfiles.create({
  name: 'Acme 1-step', kind: 'challenge',
  rules: [{ type: 'max_drawdown_usd', value: 1250 }],
})
await sdk.propTemplates.create({ /* … */ riskProfileId: profile.id })
```

:::caution[`rules` and `riskProfileId` are mutually exclusive]
Sending both returns 400 `rules_and_profile_conflict` — a template has one
source of truth for its rules. While a template reads a shared profile, patching
`rules` returns 400 `rules_locked_by_profile`; send `riskProfileId: null` to move
the rules back onto the template. Other rejections: 400 `risk_profile_not_found`
(not in your tenant), 400 `invalid_profile_kind` (must be `challenge` or
`funded`), 400 `profile_is_managed` (that profile belongs to another template),
400 `profile_rules_incompatible` (a template needs 1..32 rules).
:::

Other optional fields worth knowing: `programKind` (`challenge` or `flash`), and
either `nextStepTemplateId` or `tierLadder` — never both.

**Next:** [Sell a paper prop account](https://docs.troncharts.xyz/docs/recipes/sell-a-paper-challenge/) ·
[Create a trading group](https://docs.troncharts.xyz/docs/recipes/create-a-trading-group/) ·
[Prop account model](https://docs.troncharts.xyz/docs/launch/prop-accounts/)