Skip to content

Stand up a challenge in one call

The long way is four requests across two scopes, each handing you an id the next one needs. POST /api/v1/prop/setup runs the whole chain and returns every id it created. When this is done you have an active template you can sell as a paper prop account.

This is a shortcut through the gates, not around them: creating a firm needs firm:operate, exactly as POST /api/v1/firms does, and reusing or deriving one needs only prop:manage.

  1. Send the challenge, and say which firm — or don’t

    Section titled “Send the challenge, and say which firm — or don’t”

    Three ways to name the firm, and the third is the common one:

    • firm — create one here
    • firmId — reuse that one
    • neither — your tenant’s single live firm, resolved server-side

    Most tenants run one firm, so firmId had exactly one legal value and fetching it from GET /api/v1/firms was a round-trip that only ever went wrong one way: sending the tenant id where the firm id goes. Omitting it removes both the round-trip and the mistake.

    Sending both firm and firmId is still 400 invalid_body. Omitting when your tenant owns no live firm is 403 firm_not_found; when it owns several, 400 firm_ambiguous, and the detail names them so you can pick.

    challenge needs name and accountSizeUsd; everything else has a default, so the smallest possible body is:

    { "challenge": { "name": "Acme 25K Evaluation", "accountSizeUsd": "25000.00" } }

    The example below creates the firm too, which is the first-run case.

    Terminal window
    curl -s https://api.troncharts.xyz/api/v1/prop/setup \
    -H "authorization: Bearer $TOKEN" \
    -H "x-tenant-slug: $TC_TENANT_SLUG" \
    -H 'content-type: application/json' \
    -d '{
    "firm": { "name": "Acme Prop" },
    "challenge": {
    "name": "Acme 25K Evaluation",
    "accountSizeUsd": "25000.00",
    "feeUsd": "199.00",
    "payoutSplitPct": "80"
    }
    }'
    # → 201 { "firmId": "…", "riskProfileId": "…", "tradingGroupId": "…",
    # "templateId": "acme-25k-evaluation-challenge-3f9a1c", "template": { "active": true, … } }
  2. Loss and profit limits are percentages; position limits are not

    Section titled “Loss and profit limits are percentages; position limits are not”

    maxDrawdownPct: 10 on a 25000.00 account is stored as max_drawdown_usd: 2500. The stored rules are absolute USD because that is what the evaluator reads — doing the multiplication yourself was a step that served the storage format.

    Field Default Becomes
    maxDrawdownPct 10 max_drawdown_usd
    dailyLossPct 5 daily_loss_cap_usd
    profitTargetPct 10 profit_target_usd, challenge only (refused with kind: "funded")
    minTradingDays unset min_trading_days, only when set
    maxPositionSize unset max_position_size, stored as sent, only when set
    maxTotalContracts unset max_total_lots_open, stored as sent, only when set
    maxLeverage unset max_leverage, stored as sent, only when set

    maxPositionSize is a count of units per symbol, not USD: 2 allows 2 BTC on BTC.HL and 2 DOGE on DOGE.HL alike, resting orders included. maxTotalContracts is the same count summed over every symbol of the account. On B3 a unit is a contract; on .FUT futures it is a mini-equivalent contract, summed over the product family (10 MES = 1 ES). For a notional cap use maxLeverage, which is gross open exposure ÷ equity — 1 keeps exposure at or below the account’s equity.

    The two contract limits are checked when the order is placed: an order that would take the count above the cap is refused with 400 above_max_open_contracts, working orders counted as if they filled. Reduce-only orders and orders that do not grow the count always pass. The other rules are monitored by the risk engine — see Where rules are enforced.

    Restrict venues with venues: ["hyperliquid", "aster"], which also lands as an allowed_venues rule on the created group. Omit the field for no restriction — an empty array is not the same thing and is rejected.

  3. The template id defaults to <slug of challenge.name>-<challenge.kind>-<challenge.step>-<tenant tag>. Because kind and step are part of it, every phase of one program can share a name: call it once per phase — "kind": "challenge", "step": 1, then "step": 2, then "kind": "funded" — and link them in order with each template’s nextStepTemplateId. A funded step gets no profit target — meeting one would end the funded account — so leave profitTargetPct out of that call. Note that a later-phase template is still listed and buyable on its own. Only the same name, kind and step again on your tenant is refused with 409 already_exists, before anything is created — so a blind retry of a call that succeeded creates no twin. Send challenge.id to sell that combination twice.

    The trading-group slug and the risk profile’s name are derived from the same name but are disambiguated for you, so they never need a field of their own. If the derived slug and its numbered variants are all taken you get 409 group_slug_taken — send groupSlug.

    Reuse the firm you already made rather than creating a twin. If it is your tenant’s only live firm, say nothing at all:

    Terminal window
    curl -s https://api.troncharts.xyz/api/v1/prop/setup \
    -H "authorization: Bearer $TOKEN" \
    -H "x-tenant-slug: $TC_TENANT_SLUG" \
    -H 'content-type: application/json' \
    -d '{
    "challenge": { "name": "Acme 50K Evaluation", "accountSizeUsd": "50000.00" }
    }'

    Name it explicitly when you run more than one firm — omitting is refused with 400 firm_ambiguous there, never resolved by guesswork:

    Terminal window
    curl -s https://api.troncharts.xyz/api/v1/prop/setup \
    -H "authorization: Bearer $TOKEN" \
    -H "x-tenant-slug: $TC_TENANT_SLUG" \
    -H 'content-type: application/json' \
    -d '{
    "firmId": "'"$FIRM_ID"'",
    "challenge": { "name": "Acme 50K Evaluation", "accountSizeUsd": "50000.00" }
    }'

prop/setup creates. It does not edit. Once the objects exist, go to the resource endpoints — they are the same objects:

Next: Sell a paper prop account from a template · Launch a prop firm · How an account is configured