Skip to content

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

  1. A template belongs to one firm (firmId) and allocates to one trading group (tradingGroupId). tradingGroupId is required on create.

    firmId is optional: omit it and the template lands on your tenant’s single live firm. That is also the surest way not to send your tenant id where the firm id goes, which is the most common cause of 403 forbidden here. Omitting is refused — never guessed — when your tenant owns no live firm (403 forbidden) or more than one (400 firm_ambiguous, whose detail lists them). On update an absent firmId means “leave the firm alone” and never derives, so a PATCH that never mentions a firm cannot move the template to another one.

    The examples below send it explicitly, which is always valid.

    Terminal window
    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, … } ] }
  2. 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.

    [
    { "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.

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

    Terminal window
    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,
    "firmId": "'"$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, … } }
  4. The response carries active: true. Re-read the catalogue to see it beside the rest, then hand id and propTenantId to create-prop.

    Terminal window
    curl -s "https://api.troncharts.xyz/api/v1/prop-templates?activeOnly=true" \
    -H "authorization: Bearer $TOKEN" \
    -H "x-tenant-slug: $TC_TENANT_SLUG"

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.

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 })

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

Next: Sell a paper prop account · Create a trading group · Prop account model