Create a trading account
Create a new trading account for a trader. This is the main endpoint for provisioning accounts after a purchase.
Identify the trader by traderId OR email (exactly one). If you have the Hyperprop user ID, pass traderId. If you only have the trader’s email, pass email and it’s resolved to their Hyperprop user ID server-side. Either way, the trader’s email and name are auto-resolved — you don’t need to pass name.
What happens:
- The trader is looked up by their Hyperprop user ID (or by email, which is resolved to a user ID)
- A purchase record is created to track the transaction
- A trading account is created with
not_startedstatus - The account is immediately visible in the trader’s Hyperprop dashboard
Account types:
| Type | Description |
|---|---|
evaluation | Challenge account — trader must hit profit target to pass |
sim_funded | Simulated funded account (post-evaluation) |
competition | Competition account — traders compete against each other |
Required fields:
| Field | Description |
|---|---|
type | Account type: evaluation, sim_funded, or competition |
tradingPlanId | Which plan this account is for (get IDs from GET /trading-plans) |
customerId | Your customer’s ID in YOUR system — stable per customer, not per order (see soulbound identities below) |
traderId or email or unassigned | How to identify the trader — provide exactly one (see below) |
Identifying the trader — three modes (provide exactly one):
traderId— the Hyperprop platform user ID (UUID). Use this when you already have it.email— the email of an existing Hyperprop user. It’s resolved to the user ID server-side. If the email has no Hyperprop account you get404 TRADER_NOT_FOUND.unassigned: true— import-key mode. The account is created with no trader and the response contains a one-timeimportKey(serial-key style, e.g.HP-7K3QF-9XT2M-4WHRD). The same key is in theaccount.createdwebhook (data.importKey) so you can recover it if you miss the response. Hand the key to your customer — order-confirmation email, member area, wherever — and they redeem it inside the Hyperprop app under Trading Accounts → Import. When they do, the account links to their user, anaccount.importedwebhook fires with their email + the key + a timestamp for your verification, and billing + CME entitlement start at that moment (not at creation). For many accounts at once usePOST /trading-accounts/bulk.
Soulbound identities (keyed on customerId — always active):
Every account carries your customerId, and each customer is pinned to exactly ONE Hyperprop login (a binding):
- Unassigned accounts: the FIRST import-key redemption for a
customerIdcreates the binding. Every later import for the same customer must be redeemed by that same login — a different login is refused with409 TRADER_ALREADY_BOUNDand shown a masked hint of the correct email. - Directly-linked accounts (
traderId/email): creating the account creates the binding. If thecustomerIdis ALREADY bound to a different Hyperprop login, the create is refused with409 CUSTOMER_ALREADY_BOUND— link the account to the bound login instead, or rebind the customer first.
Result: one customer, one Hyperprop identity, all their accounts in one place — no clashes. You learn the bound login from the account.imported webhook (import.importedEmail). Inspect bindings via GET /trader-bindings; a customer who lost their Hyperprop login is moved to a new one via POST /trader-bindings/{bindingId}/rebind (which also fails all still-active accounts on the old login — a binding is never left dangling).
For traderId/email, display name and avatar are resolved automatically from the trader’s Hyperprop profile.
The response’s importKey field is null for directly-linked accounts and holds the redemption key for unlinked ones. An unlinked account can be reissued a fresh key with POST /trading-accounts/{accountId}/unlink — but only before trading starts.
Where to get the traderId:
- From GET /organization/traders → the
userIdfield (for traders who already have accounts with you) - From the Hyperprop onboarding/purchase flow (for new traders)
- Or skip it entirely and just pass the trader’s
email.
Optional fields:
| Field | Description |
|---|---|
markets | Deprecated compatibility field. Omit it. Every account receives the complete CME Group bundle (CME, CBOT, NYMEX, COMEX) regardless of a legacy partial value. |
tradingRuleId | Override the plan’s default rules |
accountNumber | Your own account number (e.g., APEX-100K-7845). If omitted, auto-generated as ACC-XXXXXXXX |
initialBalance | Override the plan’s default balance |
traderExternalId | Queryable external reference for the trader. Filter later with GET /traders?externalUserId=...; returned as trader.externalUserId |
metadata | Add notes, tags, or any custom data |
purchase | Payment details (price paid, currency, payment IDs) |
customerId vs metadata:
metadata is stored verbatim but only filterable by exact key:value. customerId (account) and traderExternalId (trader) are first-class indexed columns you can GET by directly — use them to reconcile webhooks and imports against your own IDs without email round-trips or metadata scanning. Both also appear in every webhook payload (data.customerId, data.trader.externalUserId). Webhook payloads also carry data.externalRef (same value) for integrations built before the rename — treat it as deprecated.
Markets & billing: CME Group depth-of-book market data is one inseparable bundle. Every account receives access to CME, CBOT, NYMEX, and COMEX. Partners cannot select or pay for individual exchanges.
Billing is:
platform_price_per_user + market_data_price_cme
The market_data_price_cme organization setting is the price of the full
CME Group bundle, despite the legacy column name. CBOT, NYMEX, and COMEX are
not added as separate line items. The cycle price snapshot uses:
{ "platform": 0.25, "CME_GROUP": 6.75 }
The legacy markets request field remains accepted to avoid breaking older
integrations, but any partial input such as ["CME"] is normalized to
["CME", "CBOT", "NYMEX", "COMEX"] in the stored account and response.
Minimal example (4 fields, all markets):
{
"type": "evaluation",
"tradingPlanId": "550e8400-e29b-41d4-a716-446655440000",
"traderId": "f994bc02-343c-4026-8a57-afc085eca8d5",
"customerId": "your-customer-4471"
}
Legacy input normalization example:
{
"type": "evaluation",
"tradingPlanId": "550e8400-e29b-41d4-a716-446655440000",
"traderId": "f994bc02-343c-4026-8a57-afc085eca8d5",
"markets": ["CME"]
}
The account still receives the full four-exchange bundle. New integrations
should omit markets.
Example identifying the trader by email (instead of traderId):
{
"type": "evaluation",
"tradingPlanId": "550e8400-e29b-41d4-a716-446655440000",
"email": "john@example.com",
"customerId": "cust_abc123"
}
Full example with purchase:
{
"type": "evaluation",
"tradingPlanId": "550e8400-e29b-41d4-a716-446655440000",
"email": "john@example.com",
"customerId": "cust_abc123",
"accountNumber": "APEX-100K-7845",
"metadata": {
"notes": "Referred by Mike - 10% discount",
"tags": ["referral", "discount"]
},
"purchase": {
"pricePaid": 449,
"currency": "USD",
"metadata": {
"stripePaymentId": "pi_3ABC123",
"discountCode": "MIKE10"
}
}
}
Permissions: Only organization admins can create accounts.
Error responses:
Every error returns the same shape — { statusCode, error, message, code } — so you can switch on code:
| Status | code | When |
|---|---|---|
| 400 | VALIDATION_ERROR | Payload failed validation — including a missing customerId, sending more than one (or none) of traderId / email / unassigned, or a malformed email |
| 400 | PLAN_NOT_ACTIVE | The trading plan exists but is not active |
| 400 | NO_TRADING_RULE | No tradingRuleId was given and the plan has no default rule |
| 401 | UNAUTHORIZED | Missing/invalid API key or session |
| 403 | INSUFFICIENT_PERMISSIONS / NOT_ADMIN | The API key (or user) lacks admin/write permission |
| 404 | TRADER_NOT_FOUND | The traderId or email doesn’t match an existing Hyperprop user |
| 404 | PLAN_NOT_FOUND | tradingPlanId not found for your organization |
| 404 | RULE_NOT_FOUND | tradingRuleId not found for your organization |
| 409 | CUSTOMER_ALREADY_BOUND | The customerId is already soulbound to a DIFFERENT Hyperprop login than the traderId/email you sent. Link the account to the bound login, or rebind the customer first (POST /trader-bindings/{bindingId}/rebind) |
| 500 | PURCHASE_CREATE_ERROR / ACCOUNT_CREATE_ERROR | Unexpected server error while provisioning |
Note: traderId, email, and unassigned are mutually exclusive — send exactly one. Sending several, or none, is a 400 VALIDATION_ERROR. customerId is always required (externalRef is accepted as a deprecated alias).
Authorizations
JWT Bearer token for user session auth. Format: "Bearer {token}". Used by User and Organization endpoints.
Body
Account type
evaluation, sim_funded, competition "evaluation"
ID of the trading plan (get from /trading-plans)
"550e8400-e29b-41d4-a716-446655440000"
Override default rules (optional)
"660e8400-e29b-41d4-a716-446655440000"
Hyperprop user ID to link this account to. Provide EITHER traderId OR email (exactly one). Email and name are auto-resolved from the user profile.
"f994bc02-343c-4026-8a57-afc085eca8d5"
Email of an EXISTING Hyperprop user. Resolved to their user ID server-side. Provide EITHER traderId OR email (exactly one). The trader must already have a Hyperprop account.
"john@example.com"
Create the account with NO trader attached. The response (and the account.created webhook) contains a one-time importKey — hand it to your customer, and they redeem it inside the Hyperprop app under Trading Accounts → Import. Billing and market-data entitlement start at redemption, not at creation. Mutually exclusive with traderId/email.
true true
Markets this account may trade. Omit for a futures account: the default is the full CME Group bundle. CME Group data is one inseparable licence, so naming any of CME/CBOT/NYMEX/COMEX grants all four and is billed once at the bundle rate. BINANCE (BTCUSDT perpetual) is licensed separately, is free public data, and is selected on its own — send ["BINANCE"] for a crypto-only account or include it alongside a CME market for both. A crypto-only account is not billed for CME market data.
1CME, CBOT, NYMEX, COMEX, BINANCE Custom account number (auto-generated if omitted)
"APEX-100K-7845"
Starting balance (defaults to plan's account size)
100000
REQUIRED. Your customer's ID in YOUR system. Queryable via GET /trading-accounts?customerId=..., included in every account payload and webhook (data.customerId), and the soulbound-identity key: all accounts you create for the same customerId must end up on the same Hyperprop login (see notes). Use a stable per-customer ID, not a per-order ID.
255"mffu-cust-4471"
DEPRECATED alias for customerId — accepted for backward compatibility only. Send customerId instead.
255"mffu-cust-4471"
Your own reference for this trader. Stored on the trader record, queryable via GET /traders?externalUserId=..., and returned as trader.externalUserId in account payloads and webhooks.
255"mffu-user-4471"
Custom metadata - store any data you need (JSON)
Purchase/payment details
Related topics
QuickstartMCP connector (AI agents)Create a new trading planCreate or update a copy trading configCreate a new trading rule