> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hyperprop.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 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:**
1. The trader is looked up by their Hyperprop user ID (or by email, which is resolved to a user ID)
2. A purchase record is created to track the transaction
3. A trading account is created with `not_started` status
4. 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 get `404 TRADER_NOT_FOUND`.
- `unassigned: true` — **import-key mode.** The account is created with no trader and the response contains a one-time `importKey` (serial-key style, e.g. `HP-7K3QF-9XT2M-4WHRD`). The same key is in the `account.created` webhook (`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, an `account.imported` webhook 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 use `POST /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 `customerId` creates the binding. Every later import for the same customer must be redeemed by that same login — a different login is refused with `409 TRADER_ALREADY_BOUND` and shown a masked hint of the correct email.
- **Directly-linked accounts (`traderId`/`email`):** creating the account creates the binding. If the `customerId` is ALREADY bound to a different Hyperprop login, the create is refused with `409 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 `userId` field (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:

```json
{ "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):**
```json
{
  "type": "evaluation",
  "tradingPlanId": "550e8400-e29b-41d4-a716-446655440000",
  "traderId": "f994bc02-343c-4026-8a57-afc085eca8d5",
  "customerId": "your-customer-4471"
}
```

**Legacy input normalization example:**
```json
{
  "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):**
```json
{
  "type": "evaluation",
  "tradingPlanId": "550e8400-e29b-41d4-a716-446655440000",
  "email": "john@example.com",
  "customerId": "cust_abc123"
}
```

**Full example with purchase:**
```json
{
  "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).



## OpenAPI

````yaml /api-reference/openapi.json post /v1/organization/trading-accounts
openapi: 3.0.0
info:
  title: Hyperprop Platform API
  version: 1.0.0
  description: >-
    REST API for the Hyperprop Trading Platform — provision evaluation and
    funded trading accounts, manage traders and plans, react to account
    lifecycle events via signed webhooks, and reconcile billing. Built for prop
    firms integrating from their own backend.


    ## Authentication


    Two methods, depending on who is calling:


    ### Bearer Token (JWT) — users & dashboard

    Most endpoints accept a JWT via `Authorization: Bearer <token>`. Used by the
    dashboard and client applications.


    ### API Key — organizations (prop firms)

    Organization endpoints also accept `X-API-Key: hp_live_...` for programmatic
    access from your backend — no browser session needed. Keys are managed by
    organization admins in the dashboard; each key carries permissions (`read`,
    `write`, `admin`) and can be rotated or revoked at any time. Keys are stored
    hashed (SHA-256) — the raw key is shown once at creation. Endpoints that
    create or modify data require `write` or `admin`.


    ## Quick Start


    ```bash

    # 1. List your trading plans (grab a tradingPlanId)

    curl "https://api.hyperprop.com/platform/v1/organization/trading-plans" \
      -H "X-API-Key: hp_live_your_key_here"

    # 2. Create a trading account for a trader (by Hyperprop user ID or email)

    curl -X POST
    "https://api.hyperprop.com/platform/v1/organization/trading-accounts" \
      -H "X-API-Key: hp_live_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{"type": "evaluation", "tradingPlanId": "<uuid>", "email": "trader@example.com"}'
    ```


    ## Response Envelope & Error Handling


    **Success** responses always wrap the payload:


    ```json

    { "success": true, "data": { ... }, "message": "optional human-readable
    note" }

    ```


    **Errors** — including request-validation failures, auth failures, and
    errors

    proxied from the trade engine — always return this single uniform shape with
    a

    machine-readable `code` you can switch on:


    ```json

    {
      "success": false,
      "statusCode": 404,
      "error": "Not Found",
      "message": "No Hyperprop user found with that email. The trader must have a Hyperprop account before an account can be created for them.",
      "code": "TRADER_NOT_FOUND"
    }

    ```


    Parse rule of thumb: check `success`; on `false`, switch on `code` (never on
    `message` text — messages can be reworded). Some errors carry additional
    structured context alongside these fields (e.g. payout rejections include a
    `consistency` block).


    Common codes: `VALIDATION_ERROR` (malformed payload/params), `UNAUTHORIZED`,
    `INSUFFICIENT_PERMISSIONS` / `NOT_ADMIN` (key lacks write/admin),
    `*_NOT_FOUND` (missing resource), `*_NOT_IN_ORG` (resource belongs to
    another organization), `IDEMPOTENCY_KEY_IN_PROGRESS` (duplicate in flight),
    `ENGINE_UNREACHABLE` (trade engine down — retry with the same idempotency
    key). Endpoint-specific codes (e.g. payout `OPEN_EXPOSURE`,
    `CONSISTENCY_BLOCKED`) are documented on each endpoint.


    ## Idempotency


    **Every mutating endpoint (POST, PUT, PATCH, DELETE) honors idempotency keys
    uniformly.** Send an `Idempotency-Key` header (the `X-Idempotency-Key`
    spelling is accepted as an alias) to retry any write safely without creating
    duplicates.


    How it works:


    - The key is scoped to your organization + the request path. The first
    request with a given key executes normally and its response is cached for
    **24 hours**.

    - A replay (same key, same path) within 24 hours returns the cached response
    with an `Idempotency-Replayed: true` response header — the operation is
    **not** executed again.

    - If a request with the same key is still in flight, the duplicate gets `409
    IDEMPOTENCY_KEY_IN_PROGRESS` — back off and retry; you'll then receive the
    cached response.

    - 5xx responses are **not** cached, so retrying after a server error
    re-executes the request (that's what you want). 2xx-4xx responses are cached
    — if a request failed validation and you fix the payload, use a **fresh
    key**.

    - Keys are free-form strings up to 255 characters. Use something that
    identifies the operation on your side, e.g. `order-8814-attempt-1`.


    ```bash

    curl -X POST
    "https://api.hyperprop.com/platform/v1/organization/trading-accounts" \
      -H "X-API-Key: hp_live_your_key_here" \
      -H "Idempotency-Key: order-8814-attempt-1" \
      -H "Content-Type: application/json" \
      -d '{"type": "evaluation", "tradingPlanId": "...", "traderId": "..."}'
    ```


    This applies to account creation, account PATCH (status, currentBalance,
    metadata), rules, plans, lockouts, payouts, team, webhooks — every write on
    the platform API. Payouts additionally forward the key to the trade engine
    for engine-level double-withdrawal protection.


    ## Pagination & Sorting


    List endpoints support both styles:


    - **Cursor (recommended):** pass `?cursor=` from the previous response's
    `pagination.nextCursor`. Stable under concurrent writes.

    - **Offset:** `?limit=&offset=` with `pagination.total` / `hasMore` in the
    response.


    Sorting uses `?sort=field:direction` (e.g. `?sort=created_at:asc`); allowed
    fields are listed per endpoint. Default: `created_at:desc`.


    ## Custom Metadata


    Trading accounts, purchases, traders, plans, and rules all carry a free-form
    `metadata` JSON object. Use it to store your own references — payment IDs,
    campaign tags, payout records, internal notes. Hyperprop stores and returns
    it verbatim (and lets you filter list endpoints by it, e.g.
    `?metadata=stripePaymentId:pi_123`) but never interprets it. Update
    endpoints support `mergeMetadata: true` for shallow (top-level) merges
    instead of wholesale replacement.


    ## Webhooks


    Subscribe to account lifecycle events (`account.created`,
    `account.status_changed`, `account.updated`, `account.passed`,
    `account.failed`, and more) via the Webhooks endpoints. Deliveries are
    HMAC-SHA256 signed — verify `X-Hyperprop-Signature` with your endpoint
    secret, using `X-Hyperprop-Timestamp` for replay protection;
    `X-Hyperprop-Delivery` gives you a unique delivery ID for deduplication.
    Failed deliveries are retried with backoff, and you can redeliver any event
    from the dashboard or API.


    ## MCP Connector (AI Agents)


    The platform ships a built-in [MCP](https://modelcontextprotocol.io) server,
    so AI agents and assistants (Claude, Cursor, custom agents) can operate your
    organization directly — no integration code required.


    ```http

    Endpoint:  POST https://api.hyperprop.com/platform/v1/mcp

    Transport: Streamable HTTP (stateless, JSON responses)

    Auth:      X-API-Key header, Authorization: Bearer hp_live_..., or OAuth

    ```


    **Claude web / desktop (Add custom connector):** paste the endpoint URL and
    leave the OAuth Client ID/Secret fields empty — the connector registers
    itself automatically. Claude opens a Hyperprop consent page where an org
    admin pastes the organization API key once; access then follows that key's
    permissions and ends if the key is revoked. (Under the hood: OAuth 2.1 with
    PKCE and dynamic client registration.)


    **Cursor / Claude Code — `mcp.json`:**


    ```json

    {
      "mcpServers": {
        "hyperprop": {
          "url": "https://api.hyperprop.com/platform/v1/mcp",
          "headers": { "X-API-Key": "hp_live_your_key_here" }
        }
      }
    }

    ```


    **Available tools (12):** `list_trading_plans`, `list_trading_accounts`,
    `get_trading_account`, `get_account_audit_log`, `list_traders`,
    `get_trader`, `list_purchases`, `get_purchase`, `get_billing_summary` (read)
    · `create_trading_account`, `update_trading_account`, `record_payout`
    (write).


    Every tool call executes the corresponding REST endpoint with your key, so
    organization scoping, permissions, validation, audit logging, and webhooks
    apply exactly as documented on each endpoint. Read tools work with any key;
    write tools need `write`/`admin` permission — connect a **read-only key** if
    you want a strictly read-only agent. `create_trading_account` accepts an
    optional `idempotencyKey` so agent retries can't create duplicates, and
    `record_payout` implements the documented payout recipe (append to
    `metadata.payouts`, adjust `currentBalance`, audit-logged `reason`).


    ## Endpoint Groups


    - **Authentication** — Sign up, sign in, OAuth, MFA, password reset. No auth
    required for most.

    - **User** — Profile, notifications, demo accounts, audit logs. Requires
    **Bearer token** (JWT).

    - **Organization** — Trading accounts, plans, rules, traders, purchases,
    lockouts, billing, bulk operations, webhooks, analytics. Accepts **Bearer
    token** OR **API Key**.

    - **System** — Health checks. No auth required.
  x-logo:
    url: https://app.hyperprop.com/logo-icon.svg
    altText: Hyperprop
    href: https://hyperprop.com
servers:
  - url: https://api.hyperprop.com/platform
    description: Production
security: []
tags:
  - name: Authentication
    description: >-
      User authentication - signup, signin, signout, password reset, email
      verification, OAuth, and MFA
  - name: User
    description: >-
      User account management - profile, notifications, agreements, dismissals,
      and audit logs. Requires Bearer token.
  - name: Organization
    description: >-
      Organization management - profile, team, trading accounts, plans, and
      rules. Supports **dual authentication**: Bearer JWT token (dashboard
      users) OR X-API-Key (programmatic access).
  - name: Demo
    description: >-
      Demo account management - import, list, and delete demo trading accounts.
      Requires Bearer token.
  - name: Market Data
    description: CME futures contracts and market data. Requires Bearer token.
  - name: System
    description: System endpoints - health checks and connectivity
paths:
  /v1/organization/trading-accounts:
    post:
      tags:
        - Organization
      summary: Create a trading account
      description: >-
        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:**

        1. The trader is looked up by their Hyperprop user ID (or by email,
        which is resolved to a user ID)

        2. A purchase record is created to track the transaction

        3. A trading account is created with `not_started` status

        4. 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
        get `404 TRADER_NOT_FOUND`.

        - `unassigned: true` — **import-key mode.** The account is created with
        no trader and the response contains a one-time `importKey` (serial-key
        style, e.g. `HP-7K3QF-9XT2M-4WHRD`). The same key is in the
        `account.created` webhook (`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, an `account.imported` webhook 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 use `POST /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
        `customerId` creates the binding. Every later import for the same
        customer must be redeemed by that same login — a different login is
        refused with `409 TRADER_ALREADY_BOUND` and shown a masked hint of the
        correct email.

        - **Directly-linked accounts (`traderId`/`email`):** creating the
        account creates the binding. If the `customerId` is ALREADY bound to a
        different Hyperprop login, the create is refused with `409
        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 `userId` field (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:


        ```json

        { "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):**

        ```json

        {
          "type": "evaluation",
          "tradingPlanId": "550e8400-e29b-41d4-a716-446655440000",
          "traderId": "f994bc02-343c-4026-8a57-afc085eca8d5",
          "customerId": "your-customer-4471"
        }

        ```


        **Legacy input normalization example:**

        ```json

        {
          "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):**

        ```json

        {
          "type": "evaluation",
          "tradingPlanId": "550e8400-e29b-41d4-a716-446655440000",
          "email": "john@example.com",
          "customerId": "cust_abc123"
        }

        ```


        **Full example with purchase:**

        ```json

        {
          "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).
      operationId: postV1OrganizationTradingaccounts
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Model497'
      responses:
        '201':
          description: Success - Trading account created
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/Model504'
        '400':
          description: Validation failed
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/Model505'
        '401':
          description: Authentication required
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/Model7'
        '403':
          description: Only organization admins can perform this action
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/Model506'
        '404':
          description: No Hyperprop user found for the given traderId or email
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/Model507'
        '409':
          description: Resource already exists
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/Model508'
        '500':
          description: An unexpected error occurred
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/Model3'
      security:
        - Bearer: []
        - X-API-Key: []
components:
  schemas:
    Model497:
      type: object
      properties:
        type:
          $ref: '#/components/schemas/type'
        tradingPlanId:
          type: string
          description: ID of the trading plan (get from /trading-plans)
          example: 550e8400-e29b-41d4-a716-446655440000
          x-format:
            guid: true
        tradingRuleId:
          type: string
          description: Override default rules (optional)
          example: 660e8400-e29b-41d4-a716-446655440000
          x-format:
            guid: true
        traderId:
          type: string
          description: >-
            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.
          example: f994bc02-343c-4026-8a57-afc085eca8d5
          x-format:
            guid: true
        email:
          type: string
          description: >-
            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.
          example: john@example.com
          x-format:
            email: true
        unassigned:
          type: boolean
          description: >-
            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.
          example: true
          enum:
            - true
        markets:
          $ref: '#/components/schemas/Model493'
        accountNumber:
          type: string
          description: Custom account number (auto-generated if omitted)
          example: APEX-100K-7845
        initialBalance:
          type: number
          description: Starting balance (defaults to plan's account size)
          example: 100000
        customerId:
          type: string
          description: >-
            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.
          example: mffu-cust-4471
          maxLength: 255
        externalRef:
          type: string
          description: >-
            DEPRECATED alias for customerId — accepted for backward
            compatibility only. Send customerId instead.
          example: mffu-cust-4471
          maxLength: 255
        traderExternalId:
          type: string
          description: >-
            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.
          example: mffu-user-4471
          maxLength: 255
        metadata:
          $ref: '#/components/schemas/Model494'
        purchase:
          $ref: '#/components/schemas/Model496'
      required:
        - type
        - tradingPlanId
    Model504:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          example: Trading account created successfully
        data:
          $ref: '#/components/schemas/Model503'
    Model505:
      type: object
      properties:
        success:
          type: boolean
          description: Always false on errors
          example: false
        statusCode:
          type: number
          example: 400
        error:
          type: string
          example: Bad Request
        message:
          type: string
          example: Validation failed
        code:
          type: string
          description: Machine-readable error code — switch on this, not on message text
          example: VALIDATION_ERROR
    Model7:
      type: object
      properties:
        success:
          type: boolean
          description: Always false on errors
          example: false
        statusCode:
          type: number
          example: 401
        error:
          type: string
          example: Unauthorized
        message:
          type: string
          example: Authentication required
        code:
          type: string
          description: Machine-readable error code — switch on this, not on message text
          example: UNAUTHORIZED
    Model506:
      type: object
      properties:
        success:
          type: boolean
          description: Always false on errors
          example: false
        statusCode:
          type: number
          example: 403
        error:
          type: string
          example: Forbidden
        message:
          type: string
          example: Only organization admins can perform this action
        code:
          type: string
          description: Machine-readable error code — switch on this, not on message text
          example: NOT_ADMIN
    Model507:
      type: object
      properties:
        success:
          type: boolean
          description: Always false on errors
          example: false
        statusCode:
          type: number
          example: 404
        error:
          type: string
          example: Not Found
        message:
          type: string
          example: No Hyperprop user found for the given traderId or email
        code:
          type: string
          description: Machine-readable error code — switch on this, not on message text
          example: TRADER_NOT_FOUND
    Model508:
      type: object
      properties:
        success:
          type: boolean
          description: Always false on errors
          example: false
        statusCode:
          type: number
          example: 409
        error:
          type: string
          example: Conflict
        message:
          type: string
          example: Resource already exists
        code:
          type: string
          description: Machine-readable error code — switch on this, not on message text
          example: CONFLICT
    Model3:
      type: object
      properties:
        success:
          type: boolean
          description: Always false on errors
          example: false
        statusCode:
          type: number
          example: 500
        error:
          type: string
          example: Internal Server Error
        message:
          type: string
          example: An unexpected error occurred
        code:
          type: string
          description: Machine-readable error code — switch on this, not on message text
          example: INTERNAL_ERROR
    type:
      type: string
      description: Account type
      example: evaluation
      enum:
        - evaluation
        - sim_funded
        - competition
    Model493:
      type: array
      description: >-
        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.
      example:
        - CME
        - CBOT
        - NYMEX
        - COMEX
      minItems: 1
      items:
        $ref: '#/components/schemas/Model492'
    Model494:
      type: object
      description: Custom metadata - store any data you need (JSON)
      example:
        referredBy: mike@partner.com
        discountCode: MIKE10
        internalNotes: Priority support requested
    Model496:
      type: object
      description: Purchase/payment details
      properties:
        pricePaid:
          type: number
          description: Amount the trader paid
          example: 499
        currency:
          type: string
          description: Payment currency
          example: USD
        metadata:
          $ref: '#/components/schemas/Model495'
    Model503:
      type: object
      example:
        id: 7c9e6679-7425-40de-944b-e07fc1f90ae7
        accountNumber: ACC-M3X7K9P2
        customerId: mffu-cust-4471
        externalRef: mffu-cust-4471
        type: evaluation
        status: not_started
        initialBalance: 50000
        currentBalance: 50000
        highWaterMark: 50000
        dailyStartingBalance: 50000
        startedAt: null
        completedAt: null
        violationReason: null
        markets:
          - CME
          - CBOT
          - NYMEX
          - COMEX
        metadata:
          referredBy: mike@partner.com
          discountCode: MIKE10
        userId: f994bc02-343c-4026-8a57-afc085eca8d5
        userEmail: john.smith@email.com
        importKey: null
        tradingPlan:
          id: 09c93da5-f057-4cc5-82da-ff9220292c94
          name: 50K Challenge
          accountSize: 50000
        tradingRule:
          id: 6bec94a1-425b-47be-bb41-a80e3140e2e7
          name: Standard Rules
          drawdownType: static
        trader:
          email: john.smith@email.com
          name: John Smith
        purchase:
          id: f47ac10b-58cc-4372-a567-0e02b2c3d479
          purchaseDate: '2026-02-18T10:00:00.000Z'
          pricePaid: 299
          currency: USD
        createdAt: '2026-02-18T10:00:00.000Z'
        updatedAt: '2026-02-18T10:00:00.000Z'
      properties:
        id:
          type: string
          description: Unique account ID
        accountNumber:
          type: string
          description: Human-readable account number
        customerId:
          type: string
          description: Your customer ID for this account
        externalRef:
          type: string
          description: DEPRECATED alias for customerId (same value)
        type:
          type: string
          description: Account type
        status:
          type: string
          description: Initial status (always not_started)
        initialBalance:
          type: number
          description: Starting balance
        currentBalance:
          type: number
          description: Current balance
        highWaterMark:
          type: number
          description: High water mark
        dailyStartingBalance:
          type: number
          description: Daily starting balance
        startedAt:
          type: string
          description: When trading started (null until first trade)
        completedAt:
          type: string
          description: When completed/failed (null while active)
        violationReason:
          type: string
          description: Reason for failure (null unless failed)
        markets:
          $ref: '#/components/schemas/Model498'
        metadata:
          $ref: '#/components/schemas/Model499'
        userId:
          type: string
          description: Hyperprop user ID (null until the import key is redeemed)
        userEmail:
          type: string
          description: Hyperprop user email (null until the import key is redeemed)
        importKey:
          type: string
          description: >-
            One-time redemption key (unassigned accounts only, null when a
            trader was linked directly). Hand it to your customer; they redeem
            it in the Hyperprop app under Trading Accounts → Import.
          example: HP-7K3QF-9XT2M-4WHRD
        tradingPlan:
          $ref: '#/components/schemas/Model500'
        tradingRule:
          $ref: '#/components/schemas/Model501'
        trader:
          $ref: '#/components/schemas/trader'
        purchase:
          $ref: '#/components/schemas/Model502'
        createdAt:
          type: string
        updatedAt:
          type: string
    Model492:
      type: string
      enum:
        - CME
        - CBOT
        - NYMEX
        - COMEX
        - BINANCE
    Model495:
      type: object
      description: Payment metadata (store payment IDs, promo codes, etc.)
      example:
        stripePaymentId: pi_3ABC123
        campaign: summer-promo
    Model498:
      type: array
      description: 'Always the full CME Group bundle: CME, CBOT, NYMEX, COMEX'
      items:
        type: string
    Model499:
      type: object
      description: Your custom metadata
    Model500:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        accountSize:
          type: number
    Model501:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
    trader:
      type: object
      properties:
        email:
          type: string
        name:
          type: string
    Model502:
      type: object
      properties:
        id:
          type: string
        purchaseDate:
          type: string
        pricePaid:
          type: number
        currency:
          type: string
  securitySchemes:
    Bearer:
      type: apiKey
      name: Authorization
      in: header
      description: >-
        JWT Bearer token for user session auth. Format: "Bearer {token}". Used
        by User and Organization endpoints.
    X-API-Key:
      type: apiKey
      name: X-API-Key
      in: header
      description: >-
        Organization API key for programmatic access. Format: "hp_live_{key}".
        Used by Organization endpoints as an alternative to Bearer token. Keys
        are managed in the dashboard.

````

## Related topics

- [Quickstart](/quickstart.md)
- [MCP connector (AI agents)](/guides/mcp-connector.md)
- [Create a new trading plan](/platform-api/organization/create-a-new-trading-plan.md)
- [Create or update a copy trading config](/trade-api/copy-trading/create-or-update-a-copy-trading-config.md)
- [Create a new trading rule](/platform-api/organization/create-a-new-trading-rule.md)
