Skip to content

How an account is configured

Three objects shape what an account may trade and under what limits. They look overlapping from the outside, so this page states exactly what each one owns and which of them you write.

Short version: you write a trading group and, for prop, a challenge template. A risk profile is something the server keeps for you unless you deliberately want to share one rule set across several templates.

trading group ──► challenge template ──► trader ──► account
(required) (prop only) (required)
risk profile
(optional)
  1. Trading group — the bare minimum. A template cannot be created without one (400 trading_group_required), and every account resolves to one.
  2. Challenge template — for a prop program. Carries the rules, and points at the group from step 1.
  3. Risk profile — optional, and only when you want to share one rule set across templates, or to put limits on a non-prop account. Otherwise the server keeps a managed one for you.
  4. TraderPOST /api/v1/users. An account needs an owner, and its id is the ownerUserId the account calls want.
  5. AccountPOST /api/v1/accounts/create-prop (prop) or POST /api/v1/accounts (plain).

The name trips people up, so plainly: it is the person who trades the account — a users.id. It is not your tenant and not your prop firm.

Field Is Where it comes from
ownerUserId The trader POST /api/v1/users
tenantId Your platform tenant Forced from your credential — never sent
propTenantId One prop firm you operate GET /api/v1/firms

Naming a trader from another tenant returns 404 owner_user_not_found rather than confirming they exist.

const { user } = await sdk.users.create({ email: 'ana@example.com', externalId: 'crm-4471' })
// user.id → pass as ownerUserId

Pass your own externalId and the create becomes idempotent: a retried provisioning run returns the same trader (idempotent: true) instead of splitting one person across two identities. Creating a trader does not authenticate them — they still sign in with a wallet or social auth; this only establishes the identity accounts hang off.

Owns Answers
Trading group Venue and symbol allowlists, fee schedule and rebate share, leverage caps, spread skew and price bands, per-symbol and per-venue overrides Which markets, at what price
Challenge template The prop product: account size, entry fee, payout split, duration, tier ladder, next-step chain, reset and activation fees — plus the rule set the run is judged against What the trader buys, and how they pass or fail
Risk profile A named, reusable rule set (trader, challenge or funded) Which limits apply

The layers are a most-restrictive cascade, not a hierarchy. A group’s venue allowlist is enforced at compose time; the prop rules are enforced after the price is resolved. Both run on every order — neither overrides the other, and each can reject on its own.

A template’s rules and its risk profile are the same rules. You write them once, on the template:

await sdk.propTemplates.create({
id: 'acme-25k-1step',
name: 'Acme 25K Evaluation',
accountSizeUsd: '25000.00',
feeUsd: '199.00',
payoutSplitPct: '80',
tradingGroupId: group.id,
propTenantId: firm.id,
rules: [
{ type: 'max_drawdown_usd', value: 1250 },
{ type: 'profit_target_usd', value: 2000 },
],
})

The server materializes a managed risk profile holding those rules and binds it to the template. You never name it, create it, or bind it. It shows up in GET /api/v1/risk-profiles with managedByTemplateId set, and it is read-only there — PATCH and archive return 409 managed_by_template. To change the rules, patch the template.

That is the whole model for most integrations. The rest of this page is for the two cases where you touch a profile directly.

Case 1 — sharing one rule set across templates

Section titled “Case 1 — sharing one rule set across templates”

Six templates (three sizes × two steps) often share two rule sets. Author the profile once and reference it, instead of repeating the rules six times and watching them drift on the next edit:

const { profile } = await sdk.riskProfiles.create({
name: 'Acme 1-step', kind: 'challenge',
rules: [{ type: 'max_drawdown_usd', value: 1250 }],
})
for (const size of ['25000.00', '50000.00', '100000.00']) {
await sdk.propTemplates.create({ /* … */ accountSizeUsd: size, riskProfileId: profile.id })
}

rules and riskProfileId are mutually exclusive. Sending both is 400 rules_and_profile_conflict — a template has one source of truth for its rules, so neither one silently wins. When you send riskProfileId, the profile’s rules are copied down into the template so the snapshot the engine freezes onto each account matches the profile exactly.

While a template reads a shared profile, patching its rules returns 400 rules_locked_by_profile. Either edit the profile (every template reading it follows), or send riskProfileId: null to move the rules back onto the template, where they become managed again.

A plain live or paper account has no template. To give one limits, author a trader profile and bind it:

const { profile } = await sdk.riskProfiles.create({
name: 'Retail default', kind: 'trader', enforcement: 'hard',
rules: [{ type: 'max_leverage', value: 20 }],
})
await sdk.riskProfiles.bind(profile.id, accountUuid)

POST /api/v1/accounts accepts tradingGroupId and riskProfileId directly, so an account starts on the right scope instead of being created on the tenant default and re-bound a moment later. Both are optional and both are validated against your tenant: an unknown group is 400 trading_group_not_found, and a managed profile is 400 profile_is_managed — it belongs to its template, so create the account from that template instead. Omit them and the account inherits the tenant’s default group with no profile bound, exactly as before.

POST /api/v1/accounts/create-prop takes a templateId and an ownerUserId, and the template carries the rest: its tradingGroupId and its risk profile are both propagated onto the new account, and the rule set is frozen onto the prop account at creation. So for a prop integration you configure one object — the template — and the group and profile ride along.

A later edit to the template does not re-judge accounts already in flight; they keep the rules they were sold under. New accounts pick up the new rules.

Bound to Enforced by Status
A prop account (from a template) The prop engine, against the frozen snapshot Hard, always on. A breaching order is rejected with 409 prop_breach_would_occur before the venue sees it
A non-prop account (trader profile) The order-time risk-profile gate Advisory today — ENFORCE_RISK_PROFILES ships off, so a bound trader profile annotates rather than blocks
Any account, via its trading group The compose-time group gate Hard, always on for venue and symbol scope

A managed profile is deliberately not evaluated on the risk plane: the prop engine already enforces the identical rules from the frozen snapshot, so evaluating them twice would report every rule a second time as an advisory warning alongside the real verdict.

Next: Create a challenge template · Create a trading group · Create a risk profile · Prop account model