# PropExecutor API documentation

> What the API gives your firm, what it deliberately does not, and how it fits alongside the admin panel and the executor.

Every page of the PropExecutor API reference, in reading order, in one
file. Individual pages are at https://www.propexecutor.com/docs/<slug>.md
and indexed at https://www.propexecutor.com/llms.txt. The endpoint catalogue, with each
endpoint's required scope and whether it answers today, is at
https://www.propexecutor.com/docs/endpoints.json.

## Contents

**Getting started**

- The PropExecutor API — What the API gives your firm, what it deliberately does not, and how it fits alongside the admin panel and the executor.
- Quickstart — Issue a key, provision a batch of accounts and read one account's performance, in four requests.
- Authentication & scopes — How API keys work, the four scopes, and the rules for keeping them safe.

**Conventions**

- Errors — The single error shape, every code the API returns, and which ones are worth retrying.
- Pagination — Cursor paging, why there is no page number, and how to walk a full collection safely.
- Rate limits — The per-key budgets, the headers that report them, and how to back off.
- Idempotency — Why retrying a provisioning call is safe only with an idempotency key, and how to use one.

**Endpoints**

- Trading accounts — List, read, provision, assign and archive the trading accounts your firm runs.
- Account analytics — One account's whole performance picture — gain, drawdown, profit factor, the balance/equity curve and the monthly tables.
- Full account report — Everything about one account in a single call — identity, curve, statement, indicators, every deal and the evaluation terms it is judged against.
- Equity curve — The balance and equity series behind the chart, bucketed to whatever range you ask for.
- Executor credentials — Read and rotate the Account Number / Server / Password a trader signs into the terminal with.
- Traders — Create and read the trader records your accounts are attached to, and their performance across every account they hold.
- Account types — The challenges you sell — balance, price and the rule set each one is judged by.
- Rule sets — Define and version the criteria an account is judged against, and read the rule catalogue your tier unlocks.
- Firm-wide reporting — Counts, pass rate, net P&L, a leaderboard and a feed of every breach and pass across your whole book.
- Market data — Instruments, latest prices and OHLC history — for a watchlist or a chart in your own dashboard.
- Billing & credits — What your firm has bought from us, what is left of it, and every entry that moved the balance.
- Branding — Read and update how your executor looks to your traders — logo, colours, theme and login screen.
- Webhooks — Be told about breaches, passes and fills instead of polling for them, with signed and verifiable deliveries.

**Reference**

- Concepts & data model — Organizations, traders, account types, rule sets, credits — the words this API uses and what they mean here.
- Limits & quotas — How account credits are counted and debited, the per-request caps on bulk calls and page sizes, and what the API answers when you run out.
- Endpoint availability — Every endpoint in these docs and whether it answers today — generated from the same catalogue every page reads its badge from.
- What is not available — Payouts, trader logins, commission, KYC — the boundaries of this platform, and what to do instead.

---

# The PropExecutor API

> What the API gives your firm, what it deliberately does not, and how it fits alongside the admin panel and the executor.

Source: https://www.propexecutor.com/docs
Whole reference in one file: https://www.propexecutor.com/doc.md

Everything your firm does in the admin panel — provisioning trading accounts, assigning them to traders, handing over executor credentials, watching performance — is available over HTTP. The API exists so you can put those operations behind your own dashboard, your own onboarding flow and your own internal tooling, instead of asking your staff to work in two places.

It is the same tenant data the panel shows, reached with a long-lived scoped key instead of a browser session. There is no separate environment and no sandbox: a key sees your organization’s real accounts, and the accounts it creates are real accounts that spend real credits.

## Base URL

One base URL, one version. Every path on these pages is relative to it.

<!-- Base URL -->
```text
https://api.propexecutor.com/v1
```

Requests are JSON in, JSON out, over HTTPS. Timestamps are RFC 3339 in UTC (`2026-09-27T13:45:00Z`); the timestamps inside chart series are Unix *seconds*, which the relevant pages call out.

## What you can do with it

| Area | What the API gives you |
| --- | --- |
| [Trading accounts](https://www.propexecutor.com/docs/accounts) | Provision accounts in bulk, list and filter them, read live balance and equity, assign and reassign a trader, archive. |
| [Analytics](https://www.propexecutor.com/docs/analytics) | One account's whole performance picture: gain, drawdown, profit factor, Sharpe, hold time, long/short split, and the monthly and yearly P&L tables. |
| [Equity curve](https://www.propexecutor.com/docs/equity-curve) | The balance and equity series behind the chart, at whatever resolution the range needs. |
| [Credentials](https://www.propexecutor.com/docs/credentials) | Read and rotate the Account Number / Server / Password a trader signs into the executor with. |
| [Traders](https://www.propexecutor.com/docs/traders) | Create the records accounts hang off, and read a trader's performance across every account they have held. |
| [Rule sets](https://www.propexecutor.com/docs/rule-sets) | Define and version the criteria accounts are judged by, and read the rule catalogue your tier unlocks. |
| [Reporting](https://www.propexecutor.com/docs/reporting) | Firm-wide counts, pass rate, net P&L, a leaderboard, and a feed of every breach and pass. |
| [Webhooks](https://www.propexecutor.com/docs/webhooks) | Be told about breaches and passes within seconds instead of polling for them. |
| [Market data](https://www.propexecutor.com/docs/market-data) | Instruments, latest prices and OHLC history, for a watchlist or chart of your own. |
| [Billing](https://www.propexecutor.com/docs/billing) | Account credits, the ledger behind them, and your purchase history. |

## What it deliberately is not

Being clear about this saves you building against something that will not arrive. PropExecutor runs your executor and manages the accounts on it. It is not a storefront, and it does not touch your traders.

- **No trader-facing endpoints.** A trader has no account with us, no login and no dashboard here. A `trader` in this API is a record you create — a name and an email — so an account and its credentials have somewhere to hang. How you recruit, vet, charge and pay traders is entirely yours, outside this platform.
- **No payments to or from your traders.** The only billing relationship is us charging your firm for the platform. There is no endpoint to sell a challenge, and no split-payment plumbing.
- **No real order routing.** Orders are simulated against live market prices. Nothing is ever sent to a broker, an exchange or any venue. See the [terms](https://www.propexecutor.com/terms) for what that means for what you tell your traders.
- **No key-creates-key.** API keys are issued from the admin panel by a signed-in human, never by the API. A leaked key cannot issue its own replacement.

## A first request

Once you have a key ([how to get one](https://www.propexecutor.com/docs/authentication)), this lists your accounts:

`GET /v1/accounts` · **Available** · scope `accounts:read`

<!-- curl -->
```bash
curl https://api.propexecutor.com/v1/accounts \
  -H "Authorization: Bearer pfx_live_YOUR_KEY"
```

<!-- 200 OK -->
```json
{
  "data": [
    {
      "id": "6ab66c2b-a334-ee36-8da4-5f2700000001",
      "account": 10000042,
      "account_type_id": "0f9c…",
      "account_type_name": "$10K Standard",
      "trader_id": "3d21…",
      "trader_email": "trader@example.com",
      "status": "active",
      "status_reason": null,
      "status_changed_at": null,
      "starting_balance": 10000,
      "created_at": "2026-09-14T09:12:04Z"
    }
  ],
  "next_cursor": "MjAyNi0wOS0xNFQwOToxMjowNFrOfDNkMjE"
}
```

> **Not every endpoint here is live yet**
> These docs describe the full surface, which is more than is built today. Every endpoint signature carries an **Available** or **Planned** badge, and [endpoint availability](https://www.propexecutor.com/docs/availability) is the index of which is which — generated from the same catalogue those badges read from, so the two cannot drift.
> 
> Build against the available ones now; the planned ones have settled contracts, so you can write your integration before the handler lands.

> **Accounts are addressed by number**
> Paths use the 8-digit `account` number — the one your panel shows and your trader types into the terminal — not the internal UUID. The UUID is still returned as `id` if you want to store it.

## Where to go next

- [Quickstart](https://www.propexecutor.com/docs/quickstart) — a key, a batch of accounts and one account’s performance, in four requests.
- [Concepts](https://www.propexecutor.com/docs/concepts) — what an account type, a rule set and a credit actually are, if you are new to the platform.
- [Authentication](https://www.propexecutor.com/docs/authentication) — keys, scopes and how to keep them safe.

Something missing, or an endpoint you need that is not here? [Book a call](https://www.propexecutor.com/consultation) or email [info@propexecutor.com](mailto:info@propexecutor.com).

# Quickstart

> Issue a key, provision a batch of accounts and read one account's performance, in four requests.

Source: https://www.propexecutor.com/docs/quickstart
Whole reference in one file: https://www.propexecutor.com/doc.md

Four requests: issue a key, find an account type, provision a batch, read one account’s performance. Ten minutes end to end.

> **What answers today**
> Step 1 and the analytics read in step 4 are live. Provisioning, traders and credentials are still marked [planned](https://www.propexecutor.com/docs/availability) — those paths answer 404 until their handlers ship, so do that part in the panel for now. The walkthrough is written against the finished shape on purpose, so nothing you build against it has to change when they land.

## 1 · Issue a key

In the admin panel, open [Developer](https://app.propexecutor.com/developer) and create a key. Give it `accounts:read` and `accounts:write` for this walkthrough — you can add [more scopes](https://www.propexecutor.com/docs/authentication) later, and you should not grant them until you need them.

Copy the token when it appears. It is shown once and we cannot recover it.

<!-- shell -->
```bash
export PFX_KEY=pfx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

## 2 · Check what you have

An account type is the challenge you sell — a starting balance plus the rule set it is judged against. You need its id to provision anything.

<!-- curl -->
```bash
curl https://api.propexecutor.com/v1/account-types \
  -H "Authorization: Bearer $PFX_KEY"
```

<!-- 200 OK -->
```json
{
  "data": [
    {
      "id": "0f9c2e14-7b3d-4a86-9c11-2ab5d6e7f801",
      "name": "$10K Standard",
      "starting_balance": 10000,
      "price_cents": 9900,
      "rule_set_id": "7c4a…"
    }
  ],
  "next_cursor": null
}
```

No account types yet? Create them in the panel first — they are a configuration decision (which rules, what balance, what you charge) rather than something to script.

## 3 · Provision a batch

<!-- curl -->
```bash
curl -X POST https://api.propexecutor.com/v1/accounts \
  -H "Authorization: Bearer $PFX_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"account_type_id":"0f9c2e14-7b3d-4a86-9c11-2ab5d6e7f801","quantity":5}'
```

<!-- 201 Created -->
```json
{
  "accounts": [
    { "id": "6ab6…", "account": 10000042 },
    { "id": "7bc7…", "account": 10000043 }
  ],
  "credits": { "granted": 250, "used": 47, "remaining": 203 }
}
```

> **That spent five credits, for good**
> Each account costs one credit permanently — breaching or archiving it returns nothing. The `Idempotency-Key` above is what makes a network timeout safe to retry; without it a retry provisions a second batch. See [idempotency](https://www.propexecutor.com/docs/idempotency).

## 4 · Hand one over, then watch it

Create the trader, assign the account, read its credentials:

<!-- curl -->
```bash
# the trader record
curl -X POST https://api.propexecutor.com/v1/traders \
  -H "Authorization: Bearer $PFX_KEY" -H "Content-Type: application/json" \
  -d '{"name":"Jordan Ellis","email":"jordan@example.com"}'

# assign the account to them
curl -X PUT https://api.propexecutor.com/v1/accounts/10000042/trader \
  -H "Authorization: Bearer $PFX_KEY" -H "Content-Type: application/json" \
  -d '{"trader_id":"3d21…"}'

# the login to hand over (needs credentials:read)
curl https://api.propexecutor.com/v1/accounts/10000042/credentials \
  -H "Authorization: Bearer $PFX_KEY"
```

Once they are trading, one request gives you the whole performance picture:

<!-- curl -->
```bash
curl https://api.propexecutor.com/v1/accounts/10000042/analytics \
  -H "Authorization: Bearer $PFX_KEY"
```

That is the [analytics object](https://www.propexecutor.com/docs/analytics) — gain, drawdown, profit factor, the balance and equity curve and the monthly tables, in one call.

## Next

- [Pagination](https://www.propexecutor.com/docs/pagination) — before you list more than 50 of anything.
- [Rate limits](https://www.propexecutor.com/docs/rate-limits) — before you put a poll on a timer.
- [Errors](https://www.propexecutor.com/docs/errors) — the codes worth retrying, and the ones that mean stop.

# Authentication & scopes

> How API keys work, the four scopes, and the rules for keeping them safe.

Source: https://www.propexecutor.com/docs/authentication
Whole reference in one file: https://www.propexecutor.com/doc.md

Every request carries an API key as a bearer token. A key belongs to your **organization**, not to the person who created it, so staff turnover never silently breaks a running integration.

<!-- Every request -->
```bash
curl https://api.propexecutor.com/v1/accounts \
  -H "Authorization: Bearer pfx_live_YOUR_KEY"
```

## Issuing a key

Keys are created in the admin panel, under [Developer](https://app.propexecutor.com/developer), by a signed-in owner or admin. The API cannot create keys — that is deliberate, so one leaked credential cannot mint its own replacement and make revoking it pointless.

> **The token is shown once**
> We store a hash, not the key. When you create one, the full token appears in that response and never again. If you lose it, revoke it and issue another — there is no way for us to recover it, including for support.

## Scopes

A key does only what you tick. A new key with no scopes authenticates successfully and can do nothing, which is the safe default and a useful state in its own right.

| Scope | Grants | Care |
| --- | --- | --- |
| `accounts:read` | List and read accounts, trades, positions, rule flags, analytics and the equity curve. | Safe. Start here. |
| `accounts:write` | Provision accounts, assign and reassign traders, archive accounts, create traders. | Provisioning **spends account credits permanently**. |
| `credentials:read` | Read and rotate the executor password for an account. | Hands out **working logins**. Anyone with that password can trade that account. |
| `trading:write` | Open and close positions on an account, and force a manual breach. | Acts as the trader. Most firms never need this. |

A call without the scope it needs returns `403` with code `missing_scope`, naming the scope it wanted. That is a 403 and not a 401 on purpose: the credential is fine, the permission is not, and a client that re-authenticates on a 401 would otherwise loop.

<!-- 403 Forbidden -->
```json
{
  "error": {
    "code": "missing_scope",
    "message": "this API key does not have the `accounts:write` scope"
  }
}
```

## Key format

| Field | Type | Description |
| --- | --- | --- |
| `pfx_live_…` | string | 49 characters: the `pfx_live_` marker plus 40 random characters. The marker is there so secret scanners and pre-commit hooks can spot one in a diff. |
| `prefix` | string | The first 17 characters, which the panel lists so you can tell two keys apart. Not a secret, and not enough to reconstruct the key. |

## Handling keys safely

- **Server-side only.** Never put a key in browser JavaScript, a mobile app or anything else a user can read. Call the API from your backend and pass the results to your own frontend.
- **One key per system.** Separate keys for your dashboard, your onboarding job and your staging environment. Rate limits are per-key, so one runaway script cannot starve the others — and you can revoke the one that leaked without taking everything down.
- **Least scope.** A dashboard that only draws charts needs `accounts:read` and nothing else.
- **Rotate by overlap.** Issue the new key, deploy it, confirm traffic on it, then revoke the old one. Revocation takes effect immediately, so revoking first means downtime.

## Revoking

`POST /api-keys/:id/revoke` · scope `panel session`

Revoke from the panel, or from your own backend with an admin session. The next request on that key returns `401` with `invalid_api_key`. Keys are revoked rather than deleted, so a key id in an old webhook delivery or a support thread still resolves.

## Authentication errors

| Status | Code | Means |
| --- | --- | --- |
| `401` | `unauthorized` | No `Authorization` header, or not a bearer token. |
| `401` | `invalid_api_key` | Unknown, malformed or revoked key. One message for all three, so a caller learns nothing about which keys exist. |
| `402` | `payment_required` | Your organization has not completed its plan purchase yet. See [limits](https://www.propexecutor.com/docs/limits). |
| `403` | `missing_scope` | Valid key, wrong scope. |

# Errors

> The single error shape, every code the API returns, and which ones are worth retrying.

Source: https://www.propexecutor.com/docs/errors
Whole reference in one file: https://www.propexecutor.com/doc.md

Every error has the same shape, whatever went wrong. Branch on `code`, which is stable; show or log `message`, which is written for a human and may be reworded.

<!-- Error response -->
```json
{
  "error": {
    "code": "account_not_found",
    "message": "no such trading account"
  }
}
```

## Codes

| Status | Code | Means | Retry? |
| --- | --- | --- | --- |
| `400` | `invalid_body` | The JSON did not parse, or a required field is missing. | No — fix the request. |
| `400` | `invalid_account_number` | The path segment is not an 8-digit account number. | No |
| `400` | `invalid_cursor` | The cursor was not one we issued. | No — restart paging. |
| `400` | `invalid_range` | `from` is not before `to`. | No |
| `401` | `invalid_api_key` | Unknown or revoked key. | No |
| `402` | `quota_exhausted` | Not enough account credits to provision what you asked for. | No — buy credits. |
| `403` | `missing_scope` | The key lacks the scope this endpoint needs. | No |
| `404` | `account_not_found` | No such account in your organization. | No |
| `409` | `idempotency_mismatch` | An `Idempotency-Key` was reused with a different body. | No |
| `429` | `rate_limited` | Over the per-key budget. | Yes — after `Retry-After`. |
| `503` | `busy` | The database pool is saturated. | Yes — shortly. |
| `503` | `curve_unavailable` | The equity curve is not configured on this deployment. | No |

> **Retry only 429 and 503**
> Those two are the only ones where the same request can succeed later. Everything else means the request itself needs changing, and retrying it just burns your rate limit. Use exponential backoff with jitter, and honour `Retry-After` when it is present.

## A 404 means “not yours” too

An account belonging to a different organization returns the same `404` as one that does not exist. Tenant isolation is enforced in the database, so there is no way to tell the two apart — and no way to probe for other firms’ accounts.

# Pagination

> Cursor paging, why there is no page number, and how to walk a full collection safely.

Source: https://www.propexecutor.com/docs/pagination
Whole reference in one file: https://www.propexecutor.com/doc.md

Collections are cursor-paginated. Every list response has the same two keys: `data` and `next_cursor`.

<!-- List response -->
```json
{
  "data": [ /* … */ ],
  "next_cursor": "MjAyNi0wOS0xNFQwOToxMjowNFrOfDNkMjE"
}
```

When `next_cursor` is `null` you have reached the end. That is the only stop signal — there is no total count, and no page numbers.

## Walking a collection

<!-- curl -->
```bash
# first page
curl "https://api.propexecutor.com/v1/accounts?limit=100" \
  -H "Authorization: Bearer $PFX_KEY"

# the next one
curl "https://api.propexecutor.com/v1/accounts?limit=100&cursor=MjAyNi0wOS0xNFQwOToxMjowNFrOfDNkMjE" \
  -H "Authorization: Bearer $PFX_KEY"
```

<!-- Node -->
```javascript
async function allAccounts(key) {
  const out = [];
  let cursor = null;
  do {
    const url = new URL("https://api.propexecutor.com/v1/accounts");
    url.searchParams.set("limit", "200");
    if (cursor) url.searchParams.set("cursor", cursor);

    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${key}` },
    });
    if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);

    const page = await res.json();
    out.push(...page.data);
    cursor = page.next_cursor;
  } while (cursor);
  return out;
}
```

## Parameters

| Field | Type | Description |
| --- | --- | --- |
| `limit` | integer | Rows per page. Default `50`, maximum `200`. A larger value is clamped rather than rejected. |
| `cursor` | string | Opaque. Pass back exactly what `next_cursor` gave you — do not construct, decode or store one long-term. |

## Why there is no page number

- **Nothing repeats or disappears.** The cursor encodes a position, not a count, so an account created while you are paging cannot shift the rows underneath you and make page two repeat a row from page one.
- **Page 400 costs what page 1 costs.** An offset has to walk and discard every row before it. This does not.

# Rate limits

> The per-key budgets, the headers that report them, and how to back off.

Source: https://www.propexecutor.com/docs/rate-limits
Whole reference in one file: https://www.propexecutor.com/doc.md

Limits are per API key and generous — a dashboard polling every account it owns should never notice them. They exist to stop one integration from crowding out live trading, which shares the same database.

| Bucket | Methods | Budget |
| --- | --- | --- |
| Reads | `GET` | **1,200 requests / minute** |
| Writes | `POST` `PUT` `PATCH` `DELETE` | **120 requests / minute** |

The same for every plan tier. Your tier decides how many accounts you can run, not how fast you may ask about them.

## Headers

Every response carries your current position:

<!-- Response headers -->
```http
X-RateLimit-Limit: 1200
X-RateLimit-Remaining: 1187
X-RateLimit-Reset: 42
```

| Field | Type | Description |
| --- | --- | --- |
| `X-RateLimit-Limit` | integer | The budget for this bucket. |
| `X-RateLimit-Remaining` | integer | Requests left in the current window. |
| `X-RateLimit-Reset` | integer | Seconds until the window resets and the budget refills. |

## Going over

<!-- 429 Too Many Requests -->
```json
{
  "error": {
    "code": "rate_limited",
    "message": "too many requests — retry in 42s"
  }
}
```

A `429` also carries `Retry-After` in seconds. Wait that long rather than retrying immediately; a tight retry loop spends the next window before it opens.

> **Staying well under**
> Read one account’s [analytics](https://www.propexecutor.com/docs/analytics) object rather than assembling the same picture from several calls — it is one request for the whole thing. And for breaches and passes, prefer being told over asking: polling 1,250 accounts on a timer spends most of your budget learning that nothing happened.

# Idempotency

> Why retrying a provisioning call is safe only with an idempotency key, and how to use one.

Source: https://www.propexecutor.com/docs/idempotency
Whole reference in one file: https://www.propexecutor.com/doc.md

Provisioning an account spends an account credit, and **credits are never refunded** — not on a breach, not on an archive. So a request that times out is a genuine problem: you cannot tell whether it landed, and retrying blind may spend a second batch you can never get back.

An idempotency key removes the doubt. Send one on every provisioning call.

`POST /v1/accounts` · **Planned** · scope `accounts:write`

<!-- curl -->
```bash
curl -X POST https://api.propexecutor.com/v1/accounts \
  -H "Authorization: Bearer $PFX_KEY" \
  -H "Idempotency-Key: 8f14e45f-ea6a-4c1b-9d2f-3b7a9c5e1d40" \
  -H "Content-Type: application/json" \
  -d '{"account_type_id":"0f9c…","quantity":25}'
```

## How it behaves

- **First call** does the work and stores its response against the key.
- **A retry with the same key and the same body** replays the stored response verbatim — the same account numbers, not a second batch. Nothing is charged twice.
- **A retry while the first call is still running** waits for it rather than running alongside it, so two concurrent retries cannot both provision.
- **The same key with a different body** is refused with `409 idempotency_mismatch`. That is a bug in the caller, and returning the first call’s result would hide it.

## Choosing a key

Any unique string up to 255 characters; a UUID v4 is the easy choice. Generate it **before** the first attempt and reuse it for every retry of that same logical operation — a key generated per attempt defeats the entire mechanism.

> **Without a key, a retry double-spends**
> `POST /v1/accounts` without an `Idempotency-Key` is accepted and treated as a brand-new request every time. If your HTTP client retries on timeout by default — most do — provision with a key or turn the retries off.

# Trading accounts

> List, read, provision, assign and archive the trading accounts your firm runs.

Source: https://www.propexecutor.com/docs/accounts
Whole reference in one file: https://www.propexecutor.com/doc.md

A trading account is one simulated account running on your executor. It is created against an [account type](https://www.propexecutor.com/docs/concepts), freezes that type’s rule set version at creation, and is identified by an 8-digit number your trader signs in with.

## List accounts

`GET /v1/accounts` · **Available** · scope `accounts:read`

Newest first, [cursor-paginated](https://www.propexecutor.com/docs/pagination).

<!-- curl -->
```bash
curl "https://api.propexecutor.com/v1/accounts?limit=50" \
  -H "Authorization: Bearer $PFX_KEY"
```

### Each row

| Field | Type | Description |
| --- | --- | --- |
| `id` | uuid | Internal identifier. Stable forever. |
| `account` | integer | The 8-digit number. What your trader types into the executor, and what every path on this page takes. |
| `account_type_id` | uuid | The account type this was provisioned against. |
| `account_type_name` | string | Its name, e.g. `$10K Standard`. |
| `trader_id` | uuid \| null | The trader holding it, or null if it is provisioned and unassigned. |
| `trader_email` | string \| null | That trader's email, for display. |
| `status` | string | `active`, `breached`, `passed` or `archived`. |
| `status_reason` | string \| null | Why the rule engine ended the challenge — which rule, and the numbers. |
| `status_changed_at` | timestamp \| null | When that happened. |
| `starting_balance` | number | What the account was funded with. |
| `created_at` | timestamp | When it was provisioned. |

## Read one account

`GET /v1/accounts/{account}` · **Available** · scope `accounts:read`

The same row plus live trading state — balance, equity and open positions as of now.

<!-- 200 OK -->
```json
{
  "id": "6ab66c2b-a334-ee36-8da4-5f2700000001",
  "account": 10000042,
  "account_type_name": "$10K Standard",
  "status": "active",
  "starting_balance": 10000,
  "balance": 10229.72,
  "equity": 10187.4,
  "peak_equity": 10310.55,
  "open_positions": [
    {
      "id": "b2c4…",
      "instrument": "EURUSD",
      "side": "long",
      "size": 0.5,
      "entry_price": 1.08421,
      "mark_price": 1.08337,
      "stop_loss": 1.081,
      "take_profit": null,
      "margin_used": 180.7,
      "floating_pnl": -42.32,
      "opened_at": "2026-09-27T13:02:11Z"
    }
  ],
  "created_at": "2026-09-14T09:12:04Z"
}
```

> **Balance and equity are not the same number**
> `balance` is realized: it only moves when a position closes. `equity` is balance plus the floating profit and loss of everything still open, marked at the latest price — the number the rule engine judges drawdown against.

## Provision accounts

`POST /v1/accounts` · **Planned** · scope `accounts:write`

Creates one or more accounts against an account type. Up to **100 per request**.

<!-- curl -->
```bash
curl -X POST https://api.propexecutor.com/v1/accounts \
  -H "Authorization: Bearer $PFX_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"account_type_id":"0f9c…","quantity":25}'
```

| Field | Type | Description |
| --- | --- | --- |
| `account_type_id` | uuid · required | Which account type to provision. |
| `quantity` | integer | How many. Defaults to `1`, capped at `100` per request. |
| `trader_id` | uuid | Assign them to this trader immediately. Omit to provision unassigned and assign later — which is what most firms do. |

> **This spends credits, permanently**
> Each account costs one account credit for good. Breaching or archiving it gives nothing back. Send an [Idempotency-Key](https://www.propexecutor.com/docs/idempotency) so a network retry cannot spend a second batch, and see [limits](https://www.propexecutor.com/docs/limits) for the `402` you get when you run out.

## Assign a trader

`PUT /v1/accounts/{account}/trader` · **Available** · scope `accounts:write`

Hands an account to a trader, identified by **email**. You do not have to create the trader first and you do not have to keep our ids: if your organization has no trader with that email, this creates one with the name you pass and assigns it, in the same transaction.

<!-- curl -->
```bash
curl -X PUT https://api.propexecutor.com/v1/accounts/10000042/trader \
  -H "Authorization: Bearer $PFX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Jordan Ellis","email":"jordan@example.com"}'
```

| Field | Type | Description |
| --- | --- | --- |
| `name` | string | **Required.** Used only when the trader is being created. If the email is already known, the name we have **wins and this is ignored** — see below. |
| `email` | string | **Required.** The identity this call resolves on. Lower-cased and trimmed, so `Jordan@Example.com` and `jordan@example.com` are the same trader. Nothing else is normalized: `jordan+prop@` and `jor.dan@` stay distinct people, because deciding otherwise is your call, not ours. |

<!-- 200 OK -->
```json
{
  "account": 10000042,
  "trader": {
    "id": "3d21f0c2-…",
    "name": "Jordan Ellis",
    "email": "jordan@example.com"
  },
  "trader_created": true,
  "assigned": true,
  "sessions_revoked": true
}
```

| Field | Type | Description |
| --- | --- | --- |
| `trader_created` | boolean | True when this call created the trader record, false when it reused one you already had. |
| `assigned` | boolean | True when the account actually changed hands. **False** when it was already assigned to this same trader — the call is a no-op, and nothing was written. |
| `sessions_revoked` | boolean | Always equal to `assigned`. Sessions are only ended when the holder actually changed. |

### An email you already have

The trader record is reused and **their stored name is left alone**. The `name` in your request is dropped without comment, and `trader_created` comes back `false`. This is deliberate: an assignment call that renamed people on every retry would make your roster depend on whichever integration called last. Correcting a name is a separate, explicit act in the admin panel.

Calling this twice with the same email is safe. The second call writes nothing, ends no sessions, and answers `200` with `assigned: false` — so a retry after a timeout cannot disconnect the trader it just confirmed.

### An account somebody else holds

> **This does not reassign. It refuses.**
> If the account already belongs to a different trader you get `409 account_assigned` and **nothing changes** — including the trader you named, who is not created either. Unassign the account first, then assign it.
> 
> Handing an account straight from one trader to another in one call is the operation most likely to be a mistake: a wrong account number inside a loop would silently cut a paying customer off and give their account away. One extra call makes that intent explicit.

<!-- 409 Conflict -->
```json
{
  "error": {
    "code": "account_assigned",
    "message": "this account is already assigned to another trader — unassign it first (DELETE /v1/accounts/10000042/trader)",
    "current_trader_id": "3d21f0c2-…"
  }
}
```

`current_trader_id` is who holds it, so you can log the collision or unassign without a second lookup.

## Unassign

`DELETE /v1/accounts/{account}/trader` · **Available** · scope `accounts:write`

Takes the account back off whoever holds it. No body — the account number is the whole request.

<!-- curl -->
```bash
curl -X DELETE https://api.propexecutor.com/v1/accounts/10000042/trader \
  -H "Authorization: Bearer $PFX_KEY"
```

<!-- 200 OK -->
```json
{
  "account": 10000042,
  "trader_id": null,
  "unassigned": true,
  "previous_trader_id": "3d21f0c2-…",
  "sessions_revoked": true
}
```

Idempotent: an account nobody holds answers `200` with `unassigned: false` and `previous_trader_id: null`, not an error.

### What assigning and unassigning actually do

Both **end every live executor session** on the account. That is the part that matters — it is what actually gets a former holder out of the terminal, rather than only changing a row.

> **The password is not rotated**
> Neither call changes the executor password, and the account number and server never change. So a previous holder who wrote the password down can log in again and keep trading an account they no longer hold.
> 
> Rotating is a separate, deliberate call — [credentials](https://www.propexecutor.com/docs/credentials) — because whoever rotates has to see the new password to relay it to the new holder. A full handover is unassign → rotate → assign.

Neither call touches the frozen rule set version, the starting balance, the trade history or the account’s status. Assigning a trader to a breached or closed account is allowed — it is bookkeeping, and it does not put the account back in play.

## Archive

`POST /v1/accounts/{account}/archive` · **Planned** · scope `accounts:write`

Takes the account out of circulation and revokes its sessions. The record, its trades and its history stay readable. No credit is returned.

## Traders

`GET /v1/traders` · **Planned** · scope `accounts:read`

`POST /v1/traders` · **Planned** · scope `accounts:write`

A trader is a name and an email you create so an account has somewhere to hang. Creating one sends nothing, grants nothing and creates no login — [traders have no account with us](https://www.propexecutor.com/docs).

You rarely need these: [assigning an account](#assign) creates the trader for you when the email is new. These are for reading your roster and for creating a trader ahead of having an account to give them.

<!-- curl -->
```bash
curl -X POST https://api.propexecutor.com/v1/traders \
  -H "Authorization: Bearer $PFX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Jordan Ellis","email":"jordan@example.com"}'
```

## Trade history

`GET /v1/accounts/{account}/trades` · **Available** · scope `accounts:read`

Every position the account has ever taken, open and closed, newest first, [cursor-paginated](https://www.propexecutor.com/docs/pagination). An open position has `closed_at` and `realized_pnl` of `null`.

| Field | Type | Description |
| --- | --- | --- |
| `id` | uuid | The position. |
| `ticket` | integer | The deal’s display number, unique across the platform — what a trading terminal would call the ticket. Use it in anything a person reads; use `id` to join. |
| `instrument` | string | e.g. `EURUSD`, `XAUUSD`, `BTCUSD`. |
| `side` | string | `long` or `short`. |
| `size` | number | In lots. |
| `entry_price` | number | Fill price. |
| `exit_price` | number \| null | Fill price on close. |
| `opened_at` | timestamp | When it filled. |
| `closed_at` | timestamp \| null | When it closed. Null while open. |
| `realized_pnl` | number \| null | Profit or loss booked on close. Null while open. |

Paginated because an account traded hard for a year is tens of thousands of rows. If you want every aggregate over the whole ledger in one response instead of walking it, that is the [account report](https://www.propexecutor.com/docs/account-report) — it computes the totals server-side rather than making you sum pages.

## Open positions

`GET /v1/accounts/{account}/positions` · **Available** · scope `accounts:read`

What the account holds **right now**, each marked to the latest price. Not paginated: how many positions an account may hold at once is a rule, and an account with no such rule is still bounded by its margin.

<!-- 200 OK -->
```json
{
  "data": [{
    "id": "50ac559c-…",
    "trading_account_id": "4592a6d9-…",
    "instrument": "USDCAD",
    "side": "short",
    "size": 0.03,
    "entry_price": 1.39153,
    "opened_at": "2026-09-15T17:21:50Z",
    "stop_loss": null,
    "take_profit": null,
    "margin_used": 30,
    "floating_pnl": -53.4,
    "mark_price": 1.41675
  }],
  "next_cursor": null
}
```

| Field | Type | Description |
| --- | --- | --- |
| `floating_pnl` | number | The position marked to mark_price. Derived on every read, never stored. |
| `mark_price` | number | The price floating_pnl was computed at. |
| `margin_used` | number | Capital this position ties up under the account's leverage, fixed at the price it opened at. |
| `stop_loss` | number \| null | Where it closes itself. Null when unset — distinguishable from a stop at zero. |

> **This is a live view, not a ledger read**
> Positions, their floating P&L and their margin are derived from the current price and held in memory — the same view the trader sees in their own terminal. Two of our instances serve requests and reconcile with the database every 10 seconds, so a position opened a moment ago may take that long to appear on every read.
> 
> For anything you are going to reconcile or report on, read [trades](#trades) instead: that is the ledger, and it is exact.

## Resting orders

`GET /v1/accounts/{account}/orders` · **Available** · scope `accounts:read`

Limits and stops that have been placed and have not triggered. Same live, in-memory source as positions, and the same caveat.

| Field | Type | Description |
| --- | --- | --- |
| `order_type` | string | Which kind of resting order it is. |
| `trigger_price` | number | The price that turns it into a position. |
| `required_margin` | number | What it *would* tie up if it triggered at its trigger price. Informational — it reserves nothing, and the real requirement is recomputed at the actual fill price. |
| `status` | string | Resting orders are pending; a triggered one becomes a position and leaves this list. |

## Rule flags

`GET /v1/accounts/{account}/rule-flags` · **Available** · scope `accounts:read`

Rules the account tripped whose action is **flag** rather than breach: worth a look, trading continued. Unlike a breach these **accumulate**, so each carries an occurrence count and a first/last seen time.

<!-- 200 OK -->
```json
{
  "data": [{
    "rule_id": "r3",
    "rule_key": "max_lot_per_order",
    "reason": "Order of 2.50 lots exceeds the 2.00 lot guidance",
    "occurrences": 4,
    "first_seen_at": "2026-09-14T11:02:10Z",
    "last_seen_at": "2026-09-27T08:41:55Z"
  }],
  "next_cursor": null
}
```

This is the answer to “why is `breaches` empty when I know something happened” on the [account report](https://www.propexecutor.com/docs/account-report): a flag is not a breach, and the two are reported separately on purpose.

# Account analytics

> One account's whole performance picture — gain, drawdown, profit factor, the balance/equity curve and the monthly tables.

Source: https://www.propexecutor.com/docs/analytics
Whole reference in one file: https://www.propexecutor.com/doc.md

One request returns everything a performance dashboard draws for a single account: the headline numbers, the risk indicators, the balance and equity curve, the profit and loss series, and the monthly and yearly tables.

`GET /v1/accounts/{account}/analytics` · **Available** · scope `accounts:read`

<!-- curl -->
```bash
curl "https://api.propexecutor.com/v1/accounts/10000042/analytics?from=2026-09-01T00:00:00Z&to=2026-09-27T00:00:00Z" \
  -H "Authorization: Bearer $PFX_KEY"
```

## Range

| Field | Type | Description |
| --- | --- | --- |
| `from` | timestamp | Start of the window, RFC 3339. Defaults to 24 hours ago. Clamped forward to the account’s creation — nothing existed before that, so a wider request would only pad the series with invented flat buckets. |
| `to` | timestamp | End of the window. Defaults to now, and is clamped to now. |

The range affects the **time series** only. The totals, indicators and tables are computed over the account’s whole life, so the number on your statement never changes because somebody looked at a different chart range.

## Response

<!-- 200 OK (abridged) -->
```json
{
  "id": "6ab66c2b-a334-ee36-8da4-5f2700000001",
  "account": 10000042,
  "name": "$10K Standard",
  "currency": "USD",
  "type": "demo",
  "broker": "Apex Prop",
  "digits": 2,
  "status": "active",

  "summary": {
    "gain": 0.022972,
    "activity": 0.148,
    "deposit": [10000, 1],
    "withdrawal": [0, 0],
    "dividend": 0, "correction": 0, "credit": 0
  },
  "summary_indicators": {
    "sharpe_ratio": 1.84,
    "profit_factor": 46.944,
    "recovery_factor": 45.944,
    "drawdown": 0.0121,
    "deposit_load": 0.0306,
    "trades_per_week": 4.2,
    "hold_time": 242
  },
  "balance": {
    "balance": 10229.72,
    "equity": 10187.4,
    "period": 300,
    "chart": [
      { "x": 1790348400, "y": [10000, 10000] },
      { "x": 1790348700, "y": [10229.72, 10241.05] }
    ],
    "table": {
      "years": [{ "year": 2026, "months": { "9": 229.72 }, "yearly": 229.72 }],
      "total": 229.72
    }
  },
  "growth": {
    "growth": 0.022972,
    "drawdown": 0.0121,
    "period": 300,
    "chart": [{ "x": 1790348400, "y": 0 }],
    "drawdown_chart": [{ "x": 1790348400, "y": 0.0005 }],
    "table": { "years": [ /* … */ ], "total": 0.022972 }
  },
  "profit_total": {
    "profit": 234.72, "profit_gross": 234.72,
    "profit_dividend": 0, "profit_swap": 0,
    "loss": -5, "loss_gross": -5, "loss_commission": 0
  },
  "profit_money": { "period": 300, "profit": [ /* … */ ], "loss": [ /* … */ ], "table": { /* … */ } },
  "profit_deals": { "period": 300, "profit": [ /* … */ ], "loss": [ /* … */ ], "table": { /* … */ } },
  "profit_daily": { "chart": [{ "x": 0, "y": [0, 0] }, { "x": 4, "y": [234.72, -5] }] },
  "profit_type": {
    "robot":   { "x": 0, "y": [0, 0] },
    "manual":  { "x": 0, "y": [234.72, -5] },
    "signals": { "x": 0, "y": [0, 0] }
  },
  "long_short_total": { "long": 0, "short": 1 },
  "long_short": { "period": 300, "profit": [ /* longs */ ], "loss": [ /* shorts */ ] }
}
```

## Header fields

| Field | Type | Description |
| --- | --- | --- |
| `id` | uuid | Internal identifier. |
| `account` | integer | The 8-digit account number. |
| `name` | string | The account type's name — the challenge the account is running. |
| `currency` | string | Always `"USD"`. Every account on the platform is denominated in USD today. |
| `type` | string | Always `"demo"`. Fills are simulated against live prices and never routed to a venue. |
| `broker` | string | Your firm's name. |
| `digits` | integer | Decimal places for money. Always 2. |
| `status` | string | `active` · `breached` · `passed` · `archived`. |

## summary

| Field | Type | Description |
| --- | --- | --- |
| `gain` | number | Fraction up or down on the starting balance — `0.023` is +2.3%. Measured on **equity**, so an open position counts. |
| `activity` | number | Share of the account's life it has traded on: distinct trading days over days since provisioning, 0–1. |
| `deposit` | [amount, count] | Always exactly one deposit — the starting balance the account was provisioned with. |
| `withdrawal` | [amount, count] | Always `[0, 0]`. A challenge account is never withdrawn from. |
| `dividend · correction · credit` | number | Always 0. A simulated account has no corporate actions. |

## summary_indicators

| Field | Type | Description |
| --- | --- | --- |
| `sharpe_ratio` | number | Annualized on 252 trading days, over daily realized returns. `0` when there is not enough to measure — fewer than two trading days, or no variance at all. |
| `profit_factor` | number | Gross profit over gross loss. `0` when there are no losses — there is no ratio, and infinity is not valid JSON. |
| `recovery_factor` | number | Net profit over the worst drawdown in currency. |
| `drawdown` | number | How far below its all-time equity high the account is **now**, as a fraction. The same number your `max_drawdown_pct` rule is measured against. |
| `deposit_load` | number | Margin in use over balance — how hard the account is leaning on its capital right now. |
| `trades_per_week` | number | Average over the account's whole life, not a recent rate. |
| `hold_time` | number | Mean position lifetime in seconds, over closed trades. |

## Time series

Every series is bucketed at the same width, reported as `period` in **seconds**, and every `x` is a Unix **second** timestamp — multiply by 1000 for JavaScript `Date`.

The width is chosen from the range so the point count stays roughly constant: ask for four hours and you get 5-minute buckets, ask for three years and you get weekly ones. Read it off `period` rather than assuming — that is what it is there for.

| Series | Buckets | What y holds |
| --- | --- | --- |
| `balance.chart` | By time | `[balance, equity]` — two lines in one tuple. |
| `growth.chart` | By time | Growth as a fraction of the starting balance. |
| `growth.drawdown_chart` | By time | Trailing drawdown as a fraction of the all-time peak. |
| `profit_money` | By close time | Realized P&L, winners in `profit`, losers in `loss`. |
| `profit_deals` | By close time | Trade counts, split the same way. |
| `long_short` | By open time | Positions opened — longs in `profit`, shorts in `loss`. |
| `profit_daily.chart` | By weekday | `x` is 0 (Sunday) to 6; `y` is `[profit, loss]`. |

> **Losses keep their sign**
> Every `loss` figure is negative, and `profit_total.loss` is too. Do not negate them again when you stack a chart.

## table

Realized profit and loss grouped by calendar month and year, keyed on the **close** date — that is when a trade books. Months are keyed as strings, `"1"` to `"12"`, and a month with no trading is **absent rather than zero**, so iterate the keys you get rather than assuming twelve.

<!-- table -->
```json
{
  "years": [
    { "year": 2026, "months": { "8": 229.72, "9": -14.5 }, "yearly": 215.22 }
  ],
  "total": 215.22
}
```

## Fields that are always zero

These are present so a dashboard expecting them renders a real zero rather than breaking on a missing key. They are not placeholders that will fill in later without us saying so.

- `profit_swap` and `loss_commission` — the simulator charges neither swap nor commission. If you model trading costs in your challenge pricing, they are not reflected here.
- `profit_type.robot` and `profit_type.signals` — there is no order-source concept, so every trade counts as `manual`.
- `dividend`, `correction`, `credit` and `withdrawal` — see [summary](#summary) above.

## One thing to know about history

> **Equity history starts when the curve did**
> The **balance** line is exact for the account’s whole life — it is reconstructed from the trade ledger. The **equity** line needs the floating value of open positions at each moment, which is only recorded from the day the equity curve shipped on your deployment.
> 
> For any earlier period the equity line equals the balance line, and intraday dips from open losing positions are not visible. See [equity curve](https://www.propexecutor.com/docs/equity-curve) for the detail.

# Full account report

> Everything about one account in a single call — identity, curve, statement, indicators, every deal and the evaluation terms it is judged against.

Source: https://www.propexecutor.com/docs/account-report
Whole reference in one file: https://www.propexecutor.com/doc.md

Everything the platform knows about one trading account, in a single response: who it is, what it is worth, its month-by-month statement, its risk and streak indicators, its long/short split, its results per instrument and per weekday, **every deal it has ever made**, and the evaluation terms it is being judged against.

`GET /v1/accounts/{account}/report` · **Available** · scope `accounts:read`

<!-- curl -->
```bash
curl "https://api.propexecutor.com/v1/accounts/10000042/report" \
  -H "Authorization: Bearer $PFX_KEY"
```

> **Which one should you call?**
> This endpoint and [analytics](https://www.propexecutor.com/docs/analytics) answer the same question in two different shapes. Call **report** when you are rendering a whole account page and want one request with nothing to stitch together — it includes the deal list and the rule terms, which analytics leaves to separate calls.
> 
> Call **analytics** when you are driving charts and polling: it is the lighter of the two, it carries extra series (money and deal counts split into winners and losers) and it never reads the full ledger row by row. One thing to watch if you call both: its weekday chart is Sunday-first, while this endpoint’s `profitDaily` is Monday-first. Each matches the contract its own clients bind to.

## This is the slow one

The report reads the account’s entire trade ledger, aggregates all of it, and fetches the equity curve — deliberately, because a partial report is not a report. A profit factor computed over one page of trades is not a profit factor.

So: call it on page load, not on a timer, and not on every price tick. There is no pagination anywhere in the response. What bounds it instead is `tradeHistory.deals`, which is capped at **5,000** entries — `totalDeals` is always the true count, so you can tell when you are seeing a truncated list, and every aggregate in the object is still computed over the whole ledger rather than the capped slice.

## Range

| Field | Type | Description |
| --- | --- | --- |
| `from` | timestamp | Start of the charted window, RFC 3339. Defaults to **the account’s creation** — this object is the account’s history, and a report that silently covered yesterday would look like an account that had barely traded. Note this differs from [analytics](https://www.propexecutor.com/docs/analytics), which defaults to the last 24 hours. Clamped forward to the account’s creation. |
| `to` | timestamp | End of the charted window. Defaults to now, and is clamped to now. |

The range affects `balance.chart`, `growth.chart` and the three `longShort` series only. Every total, indicator, table and deal covers the account’s whole life, so nothing on your statement changes because somebody looked at a different chart range.

## Units, before anything else

The same measurement appears in two units in two places. This is the contract’s shape, not an accident, and it is the one thing worth reading before you bind a field to a label:

| Measurement | As a fraction | As a percentage | In currency |
| --- | --- | --- | --- |
| Growth on the starting balance | `growth.growth`, `summary.gain`, `evaluation.metrics.profit_percent` | — | `balance.table.total` |
| Drawdown from the all-time peak | `growth.drawdown` | `summaryIndicators.drawdown` | — |
| Share of closed trades that won | — | `tradeHistory.summary.winRate` | — |
| The rule limits | `evaluation.rulesApplied.*` | — | — |

> **summary.gain is a fraction, not money**
> `summary.gain` is growth on the starting balance as a **fraction** (`-0.0546` means down 5.46%) — the same number as `growth.growth` and `evaluation.metrics.profit_percent`. The money form is `balance.table.total`, which equals `tradeHistory.summary.totalProfit` and `profitTotal.profit + profitTotal.loss`.
> 
> `summary.activity` is a fraction too: the share of the account’s life that has had trading on it, capped at `1`. The deal count is `tradeHistory.totalDeals`.

## Response

<!-- 200 OK (abridged) -->
```json
{
  "account": 10000042,
  "name": "$1K One Step",
  "broker": "Northwind Funding",
  "currency": "USD",

  "balance": {
    "balance": 945.19,
    "equity": 945.19,
    "period": 3600,
    "chart": [
      { "x": 1771415040, "y": [1000, 1000] },
      { "x": 1788795349, "y": [945.19, 945.19] }
    ],
    "table": {
      "years": [{ "year": 2026, "months": { "1": 15.8, "2": -8.74 }, "yearly": 7.06 }],
      "total": 7.06
    }
  },

  "_id": "9f1c…",
  "type": "demo",
  "digits": 2,
  "credentialKey": "1K_ONE_STEP",
  "status": "ACTIVE",
  "isBreached": false,
  "breachReasons": [],
  "statusChangedAt": null,
  "updatedAt": "2026-09-29T08:41:02Z",
  "__v": 1,

  "summary": {
    "gain": -0.054576,
    "activity": 0.36,
    "deposit": [1000, 1],
    "withdrawal": [0, 0],
    "dividend": 0, "correction": 0, "credit": 0
  },

  "summaryIndicators": {
    "sharp_ratio": -0.8,
    "profit_factor": 0.72,
    "recovery_factor": -0.5,
    "drawdown": 9.36,
    "deposit_load": 0.0797,
    "trades_per_week": 3.2,
    "hold_time": 7047
  },

  "risksIndicators": {
    "profit": [21.94, -12.36],
    "max_consecutive_trades": [2, 16],
    "max_consecutive_profit": [37.61, -74.44]
  },

  "growth": {
    "growth": -0.054576,
    "drawdown": 0.093599,
    "period": 3600,
    "chart": [
      [{ "x": 1771415040, "y": [0.01] }],
      [{ "x": 1771415040, "y": [0.002] }]
    ],
    "table": { "years": [], "total": -0.054576 }
  },

  "drawdown": {
    "drawdown": 0.093599, "deposit_load": 0.0797, "period": 3600,
    "chart": [[…fraction], […currency], [0…], [0…]]
  },
  "dividend": { "dividend": 0, "correction": 0, "credit": 0, "period": 3600, "chart": [], "table": {} },

  "profitMoney": { "period": 3600, "profit": [], "loss": [], "table": {} },
  "profitDeals": { "period": 3600, "profit": [], "loss": [], "table": {} },
  "profitType": {
    "robot":   { "x": 0, "y": [0, 0] },
    "manual":  { "x": 0, "y": [50, -104.56] },
    "signals": { "x": 0, "y": [0, 0] }
  },

  "longShortDaily": { "chart": [{ "x": 0, "y": [13, 8] }] },

  "risksMfeMaeMoney":   { "max_avg_profit": 0, "max_avg_mfe": 0, "min_avg_loss": 0, "min_avg_mae": 0, "period": 3600, "chart": [[]] },
  "risksMfeMaePercent": { "max_avg_profit_ratio": 0, "max_avg_mfe_ratio": 0, "min_avg_loss_ratio": 0, "min_avg_mae_ratio": 0, "period": 3600, "chart": [[]] },

  "symbolIndicators": {
    "profit_factor": [["US500", 1.51]], "netto_profit": [["US500", 10.16]], "fees": [["US500", 0]]
  },
  "symbolTypes":  { "type": [["Index", 89], ["Currency", 4]] },
  "symbolsTotal": { "total": [["US500", 10.16, 9]] },
  "tradeTypeTotal": { "robots": 0, "manual": 93, "signals": 0 },

  "longShort": {
    "period": 3600,
    "long":  [{ "x": 1771113600, "y": [2] }],
    "short": [{ "x": 1771113600, "y": [1] }],
    "all":   [{ "x": 1771113600, "y": [3] }]
  },

  "longShortTotal": { "long": 63, "short": 30 },

  "longShortIndicators": {
    "netto_pl":               [-25.15, -29.41],
    "average_pl":             [-0.399206, -0.980333],
    "average_pl_percent":     [-0.000399, -0.00098],
    "commissions":            [0, 0],
    "average_profit":         [2.32127, 2.529333],
    "average_profit_percent": [0.002321, 0.002529],
    "trades":                 [63, 30],
    "win_trades":             [14, 12]
  },

  "profitDaily": {
    "chart": [
      { "x": 0, "y": [38.06, -92.02] },
      { "x": 1, "y": [16.94, -61.25] },
      { "x": 2, "y": [61.21, -10.84] },
      { "x": 3, "y": [51.9, -49.63] },
      { "x": 4, "y": [54.01, -62.94] },
      { "x": 5, "y": [0, 0] },
      { "x": 6, "y": [0, 0] }
    ]
  },

  "symbolDeals": {
    "period": 604800,
    "chart": [
      ["US30",  [{ "x": 1771113600, "y": [2] }, { "x": 1771718400, "y": [7] }]],
      ["US500", [{ "x": 1771113600, "y": [3] }, { "x": 1771718400, "y": [0] }]]
    ]
  },

  "symbolMoney": {
    "period": 604800,
    "chart": [
      ["US30",  [{ "x": 1771113600, "y": [11.52] }, { "x": 1771718400, "y": [6.92] }]],
      ["US500", [{ "x": 1771113600, "y": [14.7] }, { "x": 1771718400, "y": [18.98] }]]
    ]
  },

  "tradeHistory": {
    "fromDate": "2026-02-18T11:45:20Z",
    "toDate": "2026-09-29T08:41:02Z",
    "fetchedAt": "2026-09-29T08:41:02Z",
    "dayOffsetHours": 0,
    "totalDeals": 93,
    "deals": [{
      "ticket": 1000123, "order": 1000123, "position_id": 1000123,
      "time": 1771113600, "time_msc": 1771113600000, "time_iso": "2026-02-15T00:00:00Z",
      "type": 0, "type_name": "BUY",
      "entry": 1, "entry_name": "OUT",
      "magic": 0, "reason": 0,
      "volume": 0.16, "price": 52049,
      "commission": 0, "swap": 0, "fee": 0,
      "profit": -10.5, "net_profit": -10.5,
      "is_closing": true, "symbol": "US30",
      "comment": "", "external_id": ""
    }],
    "trades": [{
      "positionId": 1000123, "symbol": "US30", "side": "BUY", "volume": 0.16,
      "openTime": 1771113600, "openTimeIso": "2026-02-15T00:00:00Z", "openPrice": 52049,
      "closeTime": 1771117200, "closeTimeIso": "2026-02-15T01:00:00Z", "closePrice": 51983,
      "durationSeconds": 3600,
      "profit": -10.5, "commission": 0, "swap": 0, "fee": 0, "netProfit": -10.5,
      "entries": [{
        "time": 1771113600, "timeIso": "2026-02-15T00:00:00Z",
        "type_name": "BUY", "entry": 0, "entry_name": "IN",
        "price": 52049, "volume": 0.16, "cumulativeVolume": 0.16
      }],
      "entryCount": 1, "firstLegVolume": 0.16, "maxLegVolume": 0.16,
      "volumeMultiple": 1, "isAveraging": false, "isMartingale": false
    }],
    "dailyNet": {
      "2026-02-15": { "netProfit": -10.5, "grossProfit": -10.5, "closedDeals": 1 }
    },
    "summary": {
      "totalProfit": -54.56,
      "netProfit": -54.56,
      "totalCommission": 0, "totalSwap": 0, "totalFee": 0,
      "winningTrades": 26,
      "losingTrades": 67,
      "totalClosedTrades": 93,
      "winRate": 27.96,
      "symbolsTraded": ["US30", "US500"],
      "tradingDays": 31,
      "averagingPositions": 0, "martingalePositions": 0,
      "averagingDetected": false, "martingaleDetected": false
    }
  },

  "evaluation": {
    "program": "1_STEP",
    "evaluatedAt": "2026-09-29T08:41:02Z",
    "credentialKey": "1K_ONE_STEP",
    "rulesApplied": {
      "profit_target": 0.1,
      "max_loss_limit": 0.06,
      "daily_loss_limit": 0.03,
      "leverage": "1:50",
      "min_trading_days": 3,
      "min_profitable_days": null,
      "max_inactivity_days": 14
    },
    "status": "ACTIVE",
    "isBreached": false,
    "breaches": [],
    "breachReasons": [],
    "metrics": {
      "initial_balance": 1000,
      "current_balance": 945.19,
      "current_equity": 945.19,
      "worst_balance_or_equity": 945.19,
      "profit_percent": -0.0548,
      "profit_target_hit": false,
      "profitable_days": 11,
      "consecutive_inactive_days": 4,
      "daily_dd_total_days_checked": 31,
      "daily_dd_peak_equity": 1000,
      "daily_dd_threshold": 970,
      "daily_dd_current_drawdown_pct": 0.05481,
      "daily_dd_breached": true,
      "daily_dd_breach_date": null,
      "max_open_lots_limits": {},
      "freedom_lot_peaks": {},
      "freedom_lot_would_breach": []
    }
  },

  "profitTotal": {
    "profit": 50,
    "profit_gross": 50,
    "profit_dividend": 0,
    "profit_swap": 0,
    "loss": -104.56,
    "loss_gross": -104.56,
    "loss_commission": 0
  }
}
```

## Identity

| Field | Type | Description |
| --- | --- | --- |
| `account` | integer | The 8-digit account number — the same one the trader types into the terminal. |
| `name` | string | The account type's name — the challenge this account is running. |
| `broker` | string | **Your organization’s name.** There is no broker: fills are simulated against live prices and never routed to a venue. The field keeps the contract’s name and carries the firm whose executor the account trades on. |
| `currency` | string | Always `"USD"`. Every account on the platform is denominated in USD today. |

## balance

| Field | Type | Description |
| --- | --- | --- |
| `balance` | number | Closed-trade equity: the starting balance plus every realized P&L. |
| `equity` | number | Balance plus the floating P&L of everything still open. This is the number rules are judged on. |
| `chart` | point[] | `x` a Unix second, `y` the pair `[balance, equity]` — balance first. Bucket width is chosen from the span you asked for. Empty when the equity curve is not available on your deployment; see [equity curve](https://www.propexecutor.com/docs/equity-curve). |
| `table` | object | Realized P&L by calendar month and year. Months are keyed by month number as a **string** (`"1"` … `"12"`) and a month with no trading is **absent rather than zero**, so iterate the keys you get rather than 1–12. Computed from the ledger, never from the chart. |

## summaryIndicators

| Field | Type | Description |
| --- | --- | --- |
| `sharp_ratio` | number | Annualized Sharpe ratio of daily realized returns against the starting balance, on 252 trading days. `0` when there is not enough history to measure a deviation — two days of trading has no meaningful one, and a number derived from it would be worse than none. (Spelled without the ‘e’ because the contract spells it that way.) |
| `profit_factor` | number | Gross profit over gross loss. `0` for an account with no losing trade — it has no ratio, and `Infinity` is not JSON a client can parse. |
| `recovery_factor` | number | Net profit over the worst drawdown, in currency. |
| `drawdown` | number | How far below its all-time peak equity the account is **now**, as a **percentage**. The same measurement max-loss rules are enforced against. |
| `trades_per_week` | number | The lifetime average, not a recent rate. |

## risksIndicators

The streak view, read over **closed** deals in the order they settled. Every field is a two-element `[best, worst]` pair: index `0` describes winning, index `1` losing, and the losing figures are negative.

| Field | Type | Description |
| --- | --- | --- |
| `profit` | [number, number] | The largest single winning deal, and the largest single losing deal. |
| `max_consecutive_trades` | [integer, integer] | The length of the longest unbroken run of winners, and of losers. |
| `max_consecutive_profit` | [number, number] | What those two runs made and lost. The money always belongs to a run of the length above — where two runs tie on length, the more extreme one is reported. |

## profitDaily

Realized P&L by **day of week**: seven points, `x` = `0` for **Monday** through `6` for Sunday, `y` = `[profit, loss]` for that weekday across the whole ledger. Loss is negative, both are `0` for a day never traded, and every day is present so you can index the array directly. A deal counts on the day it **settled**, not the day it opened.

## longShort

Three parallel series counting positions **opened** per bucket — the question a long/short chart answers is when exposure was taken on, not when it was closed. Every bucket in the range appears in all three series at the same `x`, so you can index them against each other without aligning timestamps, and `all` is always `long + short` for its bucket. `longShortDaily` is the same split by weekday, Monday first, as `[long, short]`.

## longShortTotal and longShortIndicators

The same split as scalars. `longShortTotal` is the deal count per direction — the totals of the series above, carried so you do not have to sum a chart to label a donut. `longShortIndicators` is how each direction performed, and **every field in it is a two-element array indexed `[long, short]`**.

Counts include open positions, money does not: exposure was taken long the moment the position opened, but it has realized nothing until it closes. That is the same split `tradeHistory.summary` makes between `totalDeals` and `totalClosedTrades`.

| Field | Type | Description |
| --- | --- | --- |
| `netto_pl` | [number, number] | Net realized P&L per direction. The two sum to summary.gain. |
| `average_pl` | [number, number] | `netto_pl` over the direction’s **total** deal count, open positions included — not over its closed count. The contract’s own definition, and the reason it is not `netto_pl / win_trades`. |
| `average_pl_percent` | [number, number] | average_pl as a FRACTION of the starting balance. |
| `commissions` | [number, number] | Always `[0, 0]` — no commission is modelled, for the same reason `profitTotal`’s gross and net pairs are equal. An explicit zero, not an omission. |
| `average_profit` | [number, number] | The average WINNING deal per direction — gross profit over win_trades, so losers do not drag it down. 0 for a direction that has never won. |
| `average_profit_percent` | [number, number] | average_profit as a FRACTION of the starting balance. |
| `trades` | [integer, integer] | Every deal per direction, open ones included. The same two numbers as longShortTotal. |
| `win_trades` | [integer, integer] | Closed deals that settled at or above zero, per direction. Note that `trades` counts open positions too, so it is **not** the denominator for a per-side win rate. |

## symbolDeals and symbolMoney

One series per instrument over the same buckets as `balance.chart`. Both objects carry `period` (the bucket width in seconds) and `chart`, a list of **positional** `[symbol, points]` pairs — a two-element array, not an object — sorted alphabetically by instrument. Every symbol gets every bucket in the range, leading zeros included, so the series are index-aligned with each other and with `longShort`.

| Field | Type | Description |
| --- | --- | --- |
| `symbolDeals` | series | Positions **opened** per bucket, so a symbol’s series **sums** to its deal count. |
| `symbolMoney` | series | **Running** realized P&L per bucket, banked when each position closed — so a symbol’s **last point** is its contribution to `summary.gain`, and the last points of every symbol add up to it. |

## tradeHistory

| Field | Type | Description |
| --- | --- | --- |
| `totalDeals` | integer | Every trade on the account, open and closed. Always the true count, even when the list below is capped. |
| `deals` | deal[] | Newest first, capped at 5,000. Truncation drops the oldest. |
| `deals[].ticket` | integer | The deal's display identifier, unique across the platform. |
| `deals[].time` | integer | When the position was OPENED, as a Unix second. |
| `deals[].type_name` | string | `"BUY"` or `"SELL"`. |
| `deals[].volume` | number | Position size in lots. |
| `deals[].price` | number | The entry price. A closed deal's exit price is not in this shape — read it from the trades endpoint if you need it. |
| `deals[].profit` | number | Realized P&L, and `0` while the position is still open. An open position’s floating P&L is a price-dependent number that would make two calls a second apart disagree; `totalClosedTrades` tells you how many of the deals are settled. |
| `deals[].symbol` | string | The instrument, in the platform's canonical naming. |
| `summary.winRate` | number | Percentage of CLOSED trades that won. Open positions do not dilute it. |
| `summary.netProfit` | number | Identical to `totalProfit`: the simulator charges no commission and no swap, so there is nothing to subtract. Both are reported so a client reading either gets a correct number. |
| `summary.symbolsTraded` | string[] | Every instrument the account has ever traded, sorted. |

## evaluation

The terms this account is judged against — read from the rule set version that was **frozen onto it at creation**, never from its rule set’s current version. Editing a rule set never changes the terms of accounts already running under it, and this object reflects that.

| Field | Type | Description |
| --- | --- | --- |
| `program` | string | Always `"1_STEP"`. An account points at exactly one rule set version for its whole life — there is no phase 1 → phase 2 progression and no funded stage on this platform, so every account genuinely is a single step. Reported as a constant so you can switch on it. |
| `rulesApplied.profit_target` | number \| null | Target as a **fraction** of the starting balance (`0.1` = +10%). `null` when the rule set has no profit target, which means the account can never pass — **not** `0`, which would say it already has. |
| `rulesApplied.max_loss_limit` | number \| null | Maximum drawdown as a fraction. null when the rule set does not include one. |
| `rulesApplied.daily_loss_limit` | number \| null | Daily drawdown as a fraction. null when the rule set does not include one. |
| `rulesApplied.leverage` | string | The FX ratio, as `"1:50"`. Leverage is set **per asset class** on this platform, and FX is reported as the single representative value an account-level field can carry. See [rule sets](https://www.propexecutor.com/docs/rule-sets) for the per-class map. |
| `status` | string | `ACTIVE`, `BREACHED`, `PASSED` or `CLOSED`, uppercased. |
| `isBreached` | boolean | True exactly when status is BREACHED. |
| `breaches` | object[] | One entry per breached rule: `{ rule, severity, observed, threshold, message }`, plus `peak_equity` and `breach_date` on a drawdown breach. `observed` and `threshold` are null for a rule with no number to report. The rule that ended the account comes first; any other limit the account is also past follows it. Empty for a breach an administrator recorded by hand. See the note below. |
| `breachReasons` | string[] | The rule codes that ended the account — `DAILY_DRAWDOWN`, `MAX_LOSS_LIMIT`, `INACTIVITY`, or a typed rule's key uppercased (e.g. `MAX_LOT_PER_ORDER`). A breach an administrator recorded by hand carries its own text instead. |
| `metrics.profitable_days` | integer | Distinct UTC days whose realized P&L summed positive. Not the same as a rule set’s `min_trading_days`, which counts days traded at all. |
| `metrics.profit_percent` | number | Growth as a fraction, measured on EQUITY so an open position counts toward it. |

> **breaches is not a history**
> The first entry is the rule that **ended** the account, recorded in the same transaction as the status change. Any further entries are other breach rules of the account's frozen rule set that its equity is also past right now. It is not a log you can page through, and a second breach cannot happen to an account that is already breached.
> 
> Rules that **flag** rather than breach are a different thing entirely: they accumulate, trading continues, and they are not in this array. They are reported separately — see [rule sets](https://www.propexecutor.com/docs/rule-sets).

## profitTotal

Gross profit and gross loss over closed trades. `loss` and `loss_gross` are **negative**. The net and gross pairs are identical because no trading costs are modelled — adding commission or swap would change the simulator’s P&L and every drawdown that follows from it, so it is a product decision rather than a field we can fill in quietly.

## Blocks that are structurally zero

The report serves the whole of its contract, including the parts a broker-backed account would fill and a simulator cannot. Those are **explicit zeros and empty collections, never missing keys**, so a client binding to them renders an empty panel instead of `undefined`:

- `dividend`, `summary.withdrawal`, `summary.dividend/correction/credit` — a challenge account is funded once and never paid into or out of again.
- `profitTotal.profit_swap`, `profitTotal.loss_commission`, `longShortIndicators.commissions`, `symbolIndicators.fees` and the cost totals in `tradeHistory.summary` — no commission or swap is modelled.
- `profitType.robot` / `signals` and `tradeTypeTotal` — every fill comes through one order path, so everything is manual.
- `risksMfeMaeMoney` and `risksMfeMaePercent` — measuring how far a position ran in favour or against needs every tick it lived through recorded against it, and only the open and close prices are stored. Reporting anything else would be a guess dressed as a measurement.
- `evaluation.metrics.freedom_lot_peaks` and `freedom_lot_would_breach` — peak *concurrent* lots per asset class needs an exposure history nothing records. `max_open_lots_limits` carries at most the key `ALL`, because the lot ceiling here is account-wide rather than per class.
- `tradeHistory.summary.averagingDetected` / `martingaleDetected` and their counts — a position cannot be added to on this platform, so there is no scaling-in pattern to find.

## What this object does not carry

- **A deposit history.** A challenge account’s only deposit is the starting balance it was provisioned with, so `summary.deposit` is the contract’s `[amount, count]` pair with a count of `1` for the account’s whole life — the same number as `metrics.initial_balance`.
- **Commission, swap, dividends and corrections.** Not modelled — see `profitTotal` above.
- **An order source.** There is no robot / signal / manual distinction; every fill comes through the same path.
- **The trader’s identity.** A report is about an account. The trader record holding it is on [traders](https://www.propexecutor.com/docs/traders), and an API key never reaches a trader login.

### One thing to know about history

The **balance** line in `balance.chart` is exact for the account’s whole life — it is reconstructed from the ledger. The **equity** line needs the floating value of open positions at each moment, which is only recorded from the day the equity curve shipped on your deployment. For any earlier period the two lines are equal and intraday dips from open losing positions are not visible.

# Equity curve

> The balance and equity series behind the chart, bucketed to whatever range you ask for.

Source: https://www.propexecutor.com/docs/equity-curve
Whole reference in one file: https://www.propexecutor.com/doc.md

The balance and equity series on its own, for a caller that wants the chart without the rest of the [analytics object](https://www.propexecutor.com/docs/analytics).

`GET /v1/accounts/{account}/equity` · **Available** · scope `accounts:read`

<!-- curl -->
```bash
curl "https://api.propexecutor.com/v1/accounts/10000042/equity?from=2026-09-26T00:00:00Z&to=2026-09-27T00:00:00Z" \
  -H "Authorization: Bearer $PFX_KEY"
```

<!-- 200 OK -->
```json
{
  "period": 300,
  "chart": [
    {
      "x": 1790348400,
      "balance": 10000,
      "equity": 9994.2,
      "high": 10002.1,
      "low": 9990.05,
      "drawdown": 0.001,
      "filled": false
    },
    {
      "x": 1790348700,
      "balance": 10000,
      "equity": 10000,
      "high": 10000,
      "low": 10000,
      "drawdown": 0,
      "filled": true
    }
  ]
}
```

## Fields

| Field | Type | Description |
| --- | --- | --- |
| `period` | integer | Bucket width in seconds. Chosen from the range — see below. |
| `x` | integer | Bucket opening time, Unix seconds UTC. |
| `balance` | number | Realized balance at the bucket's close. |
| `equity` | number | Balance plus floating P&L at the bucket's close. |
| `high · low` | number | Highest and lowest equity *within* the bucket. `low` is the one that matters — it is where a drawdown dip shows up that a single reading per bucket would miss entirely. |
| `drawdown` | number | Worst trailing drawdown inside the bucket, as a fraction of the account's all-time peak. |
| `filled` | boolean | `true` when nothing happened in this bucket and the values were carried forward. See below. |

## Resolution

We store fine detail for recent history and progressively coarser detail for older history, and pick a bucket width that keeps the point count roughly constant whatever you ask for.

| Range requested | period returned | Points |
| --- | --- | --- |
| Up to 48 hours | 300s (5 min) | ≤ 576 |
| Up to 20 days | 3,600s (1 hour) | ≤ 480 |
| Up to 400 days | 86,400s (1 day) | ≤ 400 |
| Longer | weekly, then monthly | ≤ 400 |

Five-minute detail is kept for 35 days. Ask for a 40-day range and you get hourly buckets — `period` tells you so. That is not a degradation: nobody reads a 40-day chart at five-minute resolution.

## Filled buckets

An account with no open positions has equity equal to its balance by definition, so nothing is recorded while nothing is moving. Those buckets come back with the last known values carried forward and `filled: true`.

- Before the account’s first trade, a filled bucket sits flat at the starting balance — that is what the account was worth, not zero.
- A filled bucket always has `drawdown: 0` and `high` and `low` equal to `equity`: nothing happened, so nothing was lost. Do not read a filled bucket as a fresh low.

> **Equity cannot be backfilled**
> Floating profit and loss is computed live and, before the equity curve shipped, was never written down. So for any period earlier than that, the equity line equals the balance line exactly and intraday dips are invisible. The balance line is correct for the account’s entire history either way.

# Executor credentials

> Read and rotate the Account Number / Server / Password a trader signs into the terminal with.

Source: https://www.propexecutor.com/docs/credentials
Whole reference in one file: https://www.propexecutor.com/doc.md

Executor credentials are the three fields a trading terminal asks for: **Account Number**, **Server** and **Password**. You hand them to whichever trader currently holds the account.

| Field | What it is | Changes? |
| --- | --- | --- |
| Account Number | The account's 8-digit number. | Never. |
| Server | Your firm's server name — your organization's slug. | Never, once you have provisioned an account. |
| Password | Generated per account. | Whenever you rotate it. |

## Read credentials

`GET /v1/accounts/{account}/credentials` · **Available** · scope `credentials:read`

<!-- 200 OK -->
```json
{
  "account_number": 10000042,
  "server": "apex-prop",
  "password": "K7MNPQ4RSTUVWXYZ",
  "rotated_at": null
}
```

> **This is a working login**
> Anyone holding these three fields can trade that account. The scope is separate from `accounts:read` for exactly this reason — do not put it on a key that only draws charts, and never send a password to a browser.

## Rotate the password

`POST /v1/accounts/{account}/credentials/rotate` · **Available** · scope `credentials:read`

Generates a new password, returns it, and **ends every live session** on the account. Use it when an account changes hands or a password has been shared somewhere it should not have been.

- Already-issued access tokens stay valid for up to 15 minutes; the sessions behind them are killed at once, so nothing survives past that window.
- The old password stops working immediately. Tell the trader before you rotate, or they will simply find themselves logged out.

# Traders

> Create and read the trader records your accounts are attached to, and their performance across every account they hold.

Source: https://www.propexecutor.com/docs/traders
Whole reference in one file: https://www.propexecutor.com/doc.md

A trader record is a name and an email you create so an account and its credentials have somewhere to hang. It is not a login — a trader has no account with us, no password and no way in.

> **Why this is only a record**
> Recruiting traders, vetting them, taking their evaluation fee and paying out their profits all happen in your systems. We run the executor and manage the accounts on it. So this endpoint exists to give an account an owner you recognise, not to model a person.

## List traders

`GET /v1/traders` · **Planned** · scope `accounts:read`

<!-- curl -->
```bash
curl "https://api.propexecutor.com/v1/traders?limit=100" \
  -H "Authorization: Bearer $PFX_KEY"
```

| Field | Type | Description |
| --- | --- | --- |
| `id` | uuid | Use this when assigning an account. |
| `name` | string \| null | Whatever you called them. |
| `email` | string | Unique within your organization. |
| `account_count` | integer | How many trading accounts they currently hold. |
| `created_at` | timestamp | When you created the record. |

## Create a trader

`POST /v1/traders` · **Planned** · scope `accounts:write`

<!-- curl -->
```bash
curl -X POST https://api.propexecutor.com/v1/traders \
  -H "Authorization: Bearer $PFX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Jordan Ellis","email":"jordan@example.com"}'
```

Sends no email and creates no login. Costs nothing — traders are free, only [accounts](https://www.propexecutor.com/docs/limits) cost credits.

## Read one trader

`GET /v1/traders/{id}` · **Planned** · scope `accounts:read`

The record plus every account they hold, with each account’s status and current equity — which is the view most firms want on a trader’s profile page.

## Correct a trader

`PATCH /v1/traders/{id}` · **Planned** · scope `accounts:write`

Name and email only, for a typo or a change of address. It does not move accounts: to change who holds an account, reassign the account [by its number](https://www.propexecutor.com/docs/accounts#assign) — that is the call that actually revokes the old holder’s sessions.

## Trader summary

`GET /v1/traders/{id}/summary` · **Planned** · scope `accounts:read`

Aggregate performance across every account this trader has ever held — how many passed, how many breached, and their net result. What a firm looks at before scaling someone up.

<!-- 200 OK -->
```json
{
  "trader_id": "3d21…",
  "accounts": { "total": 4, "active": 1, "passed": 2, "breached": 1, "archived": 0 },
  "pass_rate": 0.5,
  "net_realized_pnl": 3120.44,
  "best_account": { "account": 10000042, "gain": 0.118 },
  "first_account_at": "2026-04-02T10:05:00Z"
}
```

# Account types

> The challenges you sell — balance, price and the rule set each one is judged by.

Source: https://www.propexecutor.com/docs/account-types
Whole reference in one file: https://www.propexecutor.com/doc.md

An account type is the challenge you sell: a name, a starting balance, the price you charge for it, and the rule set it is judged against. “$10K Standard” is an account type. You provision trading accounts against one.

> **The price here is informational**
> `price_cents` is what *you* charge your traders, recorded so your own dashboard can display it. We never collect it — there is no checkout for a challenge in this platform, by design. See [what is not available](https://www.propexecutor.com/docs/not-available).

## List account types

`GET /v1/account-types` · **Planned** · scope `accounts:read`

| Field | Type | Description |
| --- | --- | --- |
| `id` | uuid | What you provision against. |
| `name` | string | e.g. `$10K Standard`. |
| `starting_balance` | number | What each account is funded with. |
| `price_cents` | integer | Your price, in cents. Informational. |
| `rule_set_id` | uuid | The rule set accounts of this type are judged by. |
| `created_at` | timestamp |  |

## Create one

`POST /v1/account-types` · **Planned** · scope `accounts:write`

<!-- curl -->
```bash
curl -X POST https://api.propexecutor.com/v1/account-types \
  -H "Authorization: Bearer $PFX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "$25K Two-Step",
    "starting_balance": 25000,
    "price_cents": 19900,
    "rule_set_id": "7c4a…"
  }'
```

Several account types can point at the same rule set — a $10K and a $25K both running “Standard Rules” is the normal case, and the reason rule sets are a separate thing rather than a field on this one.

## Rename or reprice

`PATCH /v1/account-types/{id}` · **Planned** · scope `accounts:write`

> **Existing accounts do not change**
> An account froze its rule set version when it was created and is judged by that version for life. Repointing this account type at a different rule set changes what **future** accounts get, and nothing about the ones already running. There is deliberately no endpoint that can retroactively rejudge a sold account.

# Rule sets

> Define and version the criteria an account is judged against, and read the rule catalogue your tier unlocks.

Source: https://www.propexecutor.com/docs/rule-sets
Whole reference in one file: https://www.propexecutor.com/doc.md

A rule set is the criteria an account is judged by: daily drawdown, maximum drawdown, profit target, minimum trading days, plus whatever else you build from the rule catalogue. It is versioned, and versions are immutable.

## Versions are append-only

> **This is the single most important thing on this page**
> Editing a rule set **appends a new version**. It never changes an existing one, and there is no endpoint that can. A trading account freezes the version current at its creation and is judged by that version for the rest of its life.
> 
> So tightening your rules today cannot retroactively breach an account somebody bought last month. That is deliberate and not configurable — it is what makes a challenge you sold defensible.

## List rule sets

`GET /v1/rule-sets` · **Planned** · scope `accounts:read`

## Create a rule set

`POST /v1/rule-sets` · **Planned** · scope `accounts:write`

## List versions

`GET /v1/rule-sets/{id}/versions` · **Planned** · scope `accounts:read`

Newest first. Exactly one has `is_current: true` — that is the one a new account will freeze.

## Append a version

`POST /v1/rule-sets/{id}/versions` · **Planned** · scope `accounts:write`

<!-- curl -->
```bash
curl -X POST https://api.propexecutor.com/v1/rule-sets/7c4a…/versions \
  -H "Authorization: Bearer $PFX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "daily_drawdown_pct": 5,
    "max_drawdown_pct": 10,
    "profit_target_pct": 8,
    "min_trading_days": 3,
    "allowed_instruments": ["EURUSD", "GBPUSD", "XAUUSD"],
    "rules": [
      { "key": "max_open_positions", "params": { "count": 3 }, "action": "reject_order" },
      { "key": "max_lots",           "params": { "lots": 5 },  "action": "reject_order" }
    ]
  }'
```

| Field | Type | Description |
| --- | --- | --- |
| `daily_drawdown_pct` | number \| null | Measured against the day's starting equity, UTC midnight. Null means no daily limit. |
| `max_drawdown_pct` | number \| null | Trailing, measured from the account's all-time equity high. |
| `profit_target_pct` | number \| null | Null is valid and means **the account never passes** — that is how instant-funding products are modelled. |
| `min_trading_days` | integer | Distinct days with at least one trade. 0 for none. |
| `allowed_instruments` | string[] | Empty means everything your feed carries. |
| `rules` | object[] | Typed rules from the catalogue. Each has a `key`, `params` and an `action` of `breach`, `reject_order` or `flag`. |

## What an action does

| action | Effect |
| --- | --- |
| `breach` | Ends the challenge the moment the rule fires. |
| `reject_order` | Refuses the order that would have violated it. Checked inside the transaction that writes the order, so it holds under concurrent orders. |
| `flag` | Records it for your review and lets trading continue. Read them from [rule flags](https://www.propexecutor.com/docs/accounts). |

## The rule catalogue

`GET /v1/rule-catalog` · **Planned** · scope `accounts:read`

Every rule you can build with, its parameters, and whether your plan tier unlocks it. Read this rather than hard-coding a list — it is the same source the rule builder in the panel renders from, and the server validates against it.

> **Time-based rules are not available**
> Rules that would need a clock rather than an event — a news blackout window, an inactivity timeout — are listed in the catalogue as unavailable and the API refuses them. Rules are evaluated on price ticks, fills and orders, and adding a scheduler to make a rule fire on a timer would put the breach decision behind a delay. That is the one thing the rule engine will not do.

# Firm-wide reporting

> Counts, pass rate, net P&L, a leaderboard and a feed of every breach and pass across your whole book.

Source: https://www.propexecutor.com/docs/reporting
Whole reference in one file: https://www.propexecutor.com/doc.md

Firm-wide views. [Analytics](https://www.propexecutor.com/docs/analytics) answers “how is this account doing”; these answer “how is the firm doing” without you fetching every account and aggregating it yourself.

## Overview

`GET /v1/reports/overview` · **Planned** · scope `accounts:read`

<!-- 200 OK -->
```json
{
  "accounts": {
    "total": 1184, "active": 402, "passed": 96,
    "breached": 588, "archived": 98, "unassigned": 41
  },
  "pass_rate": 0.1404,
  "net_realized_pnl": -184203.55,
  "equity_under_management": 4120550.18,
  "traders": 903,
  "credits": { "granted": 1250, "used": 1184, "remaining": 66 }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `pass_rate` | number | Passed over decided (passed + breached). Deliberately excludes accounts still trading — counting those as failures understates it early and overstates it never. |
| `net_realized_pnl` | number | Summed across every account, closed trades only. Negative is the normal state for a challenge book. |
| `equity_under_management` | number | Total live equity across active accounts. Simulated capital, not money you hold. |
| `unassigned` | integer | Provisioned accounts with no trader yet — your unsold inventory. |

> **Watch `credits.remaining` here**
> This is the field worth putting on your own internal dashboard. Running out mid-onboarding means a trader who paid you cannot be given an account until you buy more. See [limits](https://www.propexecutor.com/docs/limits).

## Leaderboard

`GET /v1/reports/leaderboard` · **Planned** · scope `accounts:read`

Accounts ranked by a metric you choose. What most firms put on a public page or a trader-facing widget.

| Field | Type | Description |
| --- | --- | --- |
| `sort` | string | `gain` (default), `profit_factor`, `drawdown` or `net_pnl`. |
| `status` | string | Filter, e.g. `active` or `passed`. Omit for all. |
| `limit` | integer | Default 20, maximum 100. |

> **Do not publish this response as-is**
> It contains `trader_email`. If you are rendering a public leaderboard, map to a display name your trader consented to — an email address is personal data, and publishing one because it happened to be in the payload is the kind of mistake that is hard to undo.

## Decisions feed

`GET /v1/reports/decisions` · **Planned** · scope `accounts:read`

Every breach and pass, newest first, cursor-paginated — with the rule that fired and the equity state at the moment it did. Useful for a daily digest or a back-office queue.

<!-- 200 OK -->
```json
{
  "data": [
    {
      "account": 10000042,
      "trader_id": "3d21…",
      "decision": "breached",
      "reason": "daily drawdown 5.2% exceeded the 5% limit (equity 9480.00, day start 10000.00)",
      "equity": 9480,
      "decided_at": "2026-09-27T14:02:19Z"
    }
  ],
  "next_cursor": null
}
```

> **Prefer webhooks for this**
> Polling this feed to find out about breaches works, but [webhooks](https://www.propexecutor.com/docs/webhooks) tell you within seconds and cost you no rate limit. Use the feed to backfill after an outage, and webhooks for the live path.

# Market data

> Instruments, latest prices and OHLC history — for a watchlist or a chart in your own dashboard.

Source: https://www.propexecutor.com/docs/market-data
Whole reference in one file: https://www.propexecutor.com/doc.md

The prices your accounts are filled against, and the history behind the charts. Useful if you want a watchlist or a chart in your own dashboard rather than sending traders to the executor for it.

## Instruments

`GET /v1/instruments` · **Planned** · scope `accounts:read`

<!-- 200 OK -->
```json
{
  "data": [
    {
      "instrument": "EURUSD",
      "asset_class": "fx",
      "digits": 5,
      "contract_size": 100000,
      "default_leverage": 100
    },
    {
      "instrument": "XAUUSD",
      "asset_class": "metals",
      "digits": 2,
      "contract_size": 100,
      "default_leverage": 50
    }
  ],
  "next_cursor": null
}
```

Leverage is per asset class and is frozen onto an account at creation, so this shows the default your rule sets apply going forward — not necessarily what an existing account is margined at.

## Latest prices

`GET /v1/prices` · **Planned** · scope `accounts:read`

<!-- curl -->
```bash
curl "https://api.propexecutor.com/v1/prices?instruments=EURUSD,XAUUSD,BTCUSD" \
  -H "Authorization: Bearer $PFX_KEY"
```

| Field | Type | Description |
| --- | --- | --- |
| `instrument` | string | The symbol. |
| `bid` | number | Where a long closes. |
| `ask` | number | Where a long opens. |
| `timestamp` | integer | Unix milliseconds of the last update. |
| `stale` | boolean | `true` when nothing has been published for this symbol inside the cache window — the market is closed, or that symbol’s feed is down. Show it as stale rather than as a live price. |

> **This is a snapshot, not a stream**
> Polling this per symbol per second will spend your [read budget](https://www.propexecutor.com/docs/rate-limits) and still be behind. For a live ticker, use the executor, or ask us about a streaming endpoint — [book a call](https://www.propexecutor.com/consultation).

## Candles

`GET /v1/candles` · **Planned** · scope `accounts:read`

OHLC bars for one instrument, rolled up from one-minute bars. History goes back to 2000 for FX and metals and 2015 for BTCUSD; newer symbols start when our feed did.

| Field | Type | Description |
| --- | --- | --- |
| `instrument` | string · required | One symbol per request. |
| `interval` | string | `1m`, `5m`, `15m`, `1h`, `4h`, `1d`. Default `1h`. |
| `before` | timestamp | Page backwards from here — how the executor's chart loads older history on scroll. |
| `limit` | integer | Bars per request. Default 500, maximum 5000. |

Bars are bid-side, matching what the executor charts and what a real terminal shows.

# Billing & credits

> What your firm has bought from us, what is left of it, and every entry that moved the balance.

Source: https://www.propexecutor.com/docs/billing
Whole reference in one file: https://www.propexecutor.com/doc.md

What your firm has bought from us and what is left of it. This is the **platform → your firm** relationship only. There is no endpoint here for what you charge your traders — see [what is not available](https://www.propexecutor.com/docs/not-available).

## Credits

`GET /v1/credits` · **Planned** · scope `accounts:read`

<!-- 200 OK -->
```json
{
  "granted": 1250,
  "used": 1184,
  "remaining": 66,
  "tier": "growth"
}
```

`remaining = granted − used`. It can go negative after a refund, which blocks provisioning and nothing else.

## Credit ledger

`GET /v1/credits/ledger` · **Planned** · scope `accounts:read`

Append-only, newest first. Every entry that ever moved your balance, so “where did our credits go” has an answer you can audit yourself.

| kind | Sign | Means |
| --- | --- | --- |
| `plan_grant` | + | Your plan's accounts, granted once when it settled. |
| `pack_grant` | + | A top-up pack. |
| `upgrade_grant` | + | The quota difference an upgrade added. |
| `account_debit` | −1 | One trading account provisioned. Never reversed. |
| `refund_correction` | − | A refunded or disputed purchase being clawed back. |
| `admin_adjustment` | ± | A manual correction by us, always with a note. |

## Purchases

`GET /v1/purchases` · **Planned** · scope `accounts:read`

Your plan, packs and upgrades, with what each granted and when it settled. Amounts are in the minor unit of their currency. The payer’s email is never returned.

> **Buying happens in the panel**
> There is no endpoint to buy credits — checkout runs in the admin panel so the payment path stays in one place, with one verified webhook deciding what settled. These endpoints are read-only on purpose.

# Branding

> Read and update how your executor looks to your traders — logo, colours, theme and login screen.

Source: https://www.propexecutor.com/docs/branding
Whole reference in one file: https://www.propexecutor.com/doc.md

How your executor looks to your traders: the logo, the accent colour, the theme, and the login screen text. Read it to render a matching header in your own dashboard, or write it to change the terminal.

## Read branding

`GET /v1/branding` · **Planned** · scope `accounts:read`

<!-- 200 OK -->
```json
{
  "display_name": "Apex Prop",
  "logo_dark_url": "https://storage.googleapis.com/…/logo-dark.svg",
  "logo_light_url": null,
  "favicon_url": "https://storage.googleapis.com/…/favicon.png",
  "accent_color": "#15803d",
  "terminal_theme": "dark",
  "login_headline": "Trade the Apex evaluation",
  "login_subtext": "Sign in with the credentials your account manager sent you.",
  "support_email": "support@apexprop.example",
  "support_url": "https://apexprop.example/help",
  "legal_footer": "Simulated trading. Not a broker.",
  "hide_platform_badge": false
}
```

## Update branding

`PUT /v1/branding` · **Planned** · scope `accounts:write`

> **Your tier decides how much of this you can set**
> `minimal` allows the display name, dark logo and accent colour. `full` allows everything except hiding our badge. `full_plus` allows all of it. A field above your tier is refused with a `400` naming it, rather than being silently dropped.
> 
> The public read filters by your **current** tier, so a field saved under a higher tier stops appearing if your tier drops. It is not deleted — it comes back if you upgrade again.

Uploading a logo or favicon is a separate multipart call; ask if you need it scripted rather than done in the panel.

# Webhooks

> Be told about breaches, passes and fills instead of polling for them, with signed and verifiable deliveries.

Source: https://www.propexecutor.com/docs/webhooks
Whole reference in one file: https://www.propexecutor.com/doc.md

Register a URL and we will tell you when something happens instead of you asking. For a firm watching hundreds of accounts this is the difference between knowing about a breach in seconds and burning your whole [read budget](https://www.propexecutor.com/docs/rate-limits) discovering that nothing happened.

## Register an endpoint

`POST /v1/webhooks` · **Planned** · scope `accounts:write`

<!-- curl -->
```bash
curl -X POST https://api.propexecutor.com/v1/webhooks \
  -H "Authorization: Bearer $PFX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.apexprop.example/hooks/propexecutor",
    "events": ["account.breached", "account.passed"]
  }'
```

HTTPS only. An empty `events` array means everything, so an endpoint registered without thinking still receives rather than silently getting nothing. The response carries a `secret` — store it, it is shown once.

## Events

| Event | Fires when |
| --- | --- |
| `account.breached` | A rule was violated. Carries the rule, the reason and the equity state at the moment it fired. |
| `account.passed` | The account hit its profit target and satisfied every other objective. |
| `account.flagged` | A rule with a flag action fired — worth review, trading continues. |
| `account.created` | An account was provisioned, whether from the API or the panel. |
| `account.assigned` | An account's trader changed. Previous sessions are already revoked. |
| `account.archived` | An account was taken out of circulation. |
| `position.opened` | A position filled, including one from a resting order triggering. |
| `position.closed` | A position closed — manually, or on its stop or target. |
| `order.rejected` | A pre-order rule refused an order, with which rule and why. |
| `credits.low` | Remaining account credits fell below a threshold you set. |

## Payload

<!-- POST to your URL -->
```json
{
  "id": "evt_9f2c1b7a4e",
  "type": "account.breached",
  "created_at": "2026-09-27T14:02:19Z",
  "organization_id": "11111111-…",
  "data": {
    "account": 10000042,
    "trader_id": "3d21…",
    "status": "breached",
    "reason": "daily drawdown 5.2% exceeded the 5% limit",
    "equity": 9480,
    "balance": 9480,
    "peak_equity": 10310.55
  }
}
```

## Verifying a delivery

Every request carries a signature over the **raw body** and a timestamp:

<!-- Request headers -->
```http
X-PropExecutor-Id: evt_9f2c1b7a4e
X-PropExecutor-Timestamp: 1790503647
X-PropExecutor-Signature: v1=5d41402abc4b2a76b9719d911017c592…
```

<!-- Node -->
```javascript
import crypto from "node:crypto";

function verify(rawBody, headers, secret) {
  const ts = headers["x-propexecutor-timestamp"];
  const sig = headers["x-propexecutor-signature"];

  // Reject anything older than five minutes, so a captured delivery cannot be
  // replayed against you later.
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const expected =
    "v1=" +
    crypto
      .createHmac("sha256", secret)
      .update(`${ts}.${rawBody}`)
      .digest("hex");

  // Constant-time, so a caller cannot time their way to a valid signature.
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
```

> **Verify over the raw bytes**
> Compute the HMAC on the body exactly as received, before any JSON parsing or re-serialising. Parsing and re-encoding changes key order and whitespace, and the signature will never match.

## Delivery rules

- **Answer 2xx quickly.** Anything else, or a timeout, counts as a failure. Acknowledge first and do your work after — a slow handler looks identical to a broken one.
- **At least once, not exactly once.** A retry can deliver an event you already processed. Deduplicate on `id`.
- **Retries back off** and eventually stop, rather than hammering a dead host forever. An endpoint that keeps failing is marked and stops being delivered to.
- **Order is not guaranteed.** Use `created_at` if sequence matters, and treat each event as a statement about a moment rather than a step in a sequence.
- **An event is a notification, never authority.** The decision behind it is already durably written. If you need certainty, read the account back.

## Debugging

`GET /v1/webhooks/{id}/deliveries` · **Planned** · scope `accounts:read`

`POST /v1/webhooks/{id}/test` · **Planned** · scope `accounts:write`

Deliveries are kept with their response status and error, so you can see exactly what we sent and what came back. The test endpoint sends a signed event with the same shape as a real one — use it to check your signature verification before you rely on it.

## Managing endpoints

`GET /v1/webhooks` · **Planned** · scope `accounts:read`

`DELETE /v1/webhooks/{id}` · **Planned** · scope `accounts:write`

# Concepts & data model

> Organizations, traders, account types, rule sets, credits — the words this API uses and what they mean here.

Source: https://www.propexecutor.com/docs/concepts
Whole reference in one file: https://www.propexecutor.com/doc.md

Six words do most of the work in this API. They are worth five minutes if you are new to the platform, because two of them do not mean what they might elsewhere.

## Organization

Your firm — the tenant. It owns its branding, its account types, its rule sets, its traders and its accounts. Every API key belongs to exactly one, and can never see another. You are our customer; your traders are not.

## Trader

**A record, not a login.** A name and an email you create so an account and its credentials have somewhere to hang. A trader has no account with us, no password, no dashboard and no way in — creating one sends no email and grants nothing.

Everything real about your traders — finding them, vetting them, charging them, paying them out — happens in your systems, not ours.

## Account type

The challenge you sell: a name, a starting balance, an informational price, and the rule set it points at. “$10K Standard” is an account type. You provision trading accounts *against* one.

## Rule set & version

The criteria an account is judged by — daily drawdown, maximum drawdown, profit target, minimum trading days, plus whatever else you build in the rule builder. Several account types can share one rule set.

> **Versions are frozen, and that is load-bearing**
> Editing a rule set creates a **new version**; it never changes an existing one. A trading account freezes the version current at the moment it was created and is judged by that version for the rest of its life.
> 
> So tightening your rules today cannot retroactively breach an account somebody bought last month — which is exactly what you want, and worth knowing before you go looking for an endpoint to change an existing account’s rules. There isn’t one.

## Trading account

One simulated account. Provisioned against an account type, frozen to a rule set version, identified by an 8-digit number, and **unassigned by default** — most firms provision a batch and hand them out as traders arrive. Its trader can be changed later; its number and server cannot.

| status | Means |
| --- | --- |
| `active` | Trading, within its rules. |
| `breached` | A rule was violated. `status_reason` says which, with the numbers. |
| `passed` | Hit its profit target and satisfied every other objective. |
| `archived` | Taken out of circulation by you. History stays readable. |

## Account credits

A permanent pool. Your plan grants a number of them, packs and upgrades add more, and nothing expires. Creating a trading account spends one **forever** — breaching or archiving it returns nothing.

`remaining = granted − used`. See [limits](https://www.propexecutor.com/docs/limits) for what happens at zero.

## Executor credentials

The Account Number, Server and Password a trader types into the terminal — the same three fields any trading platform asks for. You generate them, you hand them over, and you rotate the password when an account changes hands. [Details](https://www.propexecutor.com/docs/credentials).

# Limits & quotas

> How account credits are counted and debited, the per-request caps on bulk calls and page sizes, and what the API answers when you run out.

Source: https://www.propexecutor.com/docs/limits
Whole reference in one file: https://www.propexecutor.com/doc.md

Two different things get called a limit. Your **account credits** cap how many trading accounts you can ever create; your [rate limit](https://www.propexecutor.com/docs/rate-limits) caps how fast you may call the API. They are unrelated, and only the first one costs money.

## Account credits

A permanent pool that only ever goes down by provisioning. Your plan grants its accounts once, each top-up pack adds its accounts once, and an upgrade adds the difference between the two tiers’ quotas. Nothing expires.

`GET /v1/credits` · **Planned** · scope `accounts:read`

<!-- 200 OK -->
```json
{
  "granted": 250,
  "used": 47,
  "remaining": 203,
  "tier": "basic"
}
```

- **One account, one credit, forever.** A breached or archived account does not give its credit back. That is deliberate — it is what you paid for when the account was created.
- **Unused credits carry over an upgrade.** On Starter with 40 used and 10 left, upgrading to Basic leaves you 210 against a 250 total.

## Running out

Provisioning more than you have left is refused outright — no partial batch, nothing spent.

<!-- 402 Payment Required -->
```json
{
  "error": "quota_exhausted",
  "message": "not enough account credits",
  "remaining": 3,
  "requested": 10,
  "tier": "starter",
  "cta": {
    "href": "/billing",
    "upgrade_skus": ["upgrade_starter_basic"],
    "pack_skus": ["pack_starter_100", "pack_starter_500"]
  }
}
```

Buy credits from [Billing](https://app.propexecutor.com/billing) in the panel — a top-up pack at your tier’s per-account rate, or an upgrade if you want the higher tier’s features too. Both take effect as soon as the payment settles.

## Per-request caps

| Limit | Value | Over it |
| --- | --- | --- |
| Accounts per provisioning call | **100** | `400` — split into several calls. |
| Rows per list page | **200** | Clamped down silently, not refused. |
| Chart points per series | **~600** | The bucket width widens instead. See [equity curve](https://www.propexecutor.com/docs/equity-curve). |

## What your tier also caps

Rule sets, rules per set, which rule catalogue you can use and how far you can brand the executor are all tier-dependent and enforced server-side. An existing rule set keeps working after a tier change but cannot be pushed further over a limit. Current numbers are on the [pricing page](https://www.propexecutor.com/pricing).

## Before your plan settles

An organization that signed up but has not completed its plan purchase can authenticate and read, but every write returns `402`. Finish checkout and it clears immediately — no support ticket, no waiting.

# Endpoint availability

> Every endpoint in these docs and whether it answers today — generated from the same catalogue every page reads its badge from.

Source: https://www.propexecutor.com/docs/availability
Whole reference in one file: https://www.propexecutor.com/doc.md

These docs describe the full surface a prop firm needs, which is more than is built today. This page is the honest index: what answers right now, and what is designed and documented but not yet shipped.

| Status | Count | Means |
| --- | --- | --- |
| Available | **16** | Live in production. Safe to build against now. |
| Planned | **34** | Documented and designed. Returns 404 today. |

> **Why document what is not built**
> Two reasons. A firm deciding whether to buy needs to see the whole shape rather than guess at it, and a firm building against it needs the contract settled before the handler exists so the integration and the endpoint can be written in either order.
> 
> What we will not do is let the two drift. Every signature on every page reads its badge from the same catalogue this table is built from, so a page cannot quietly claim something is live.

## Trading accounts

|  | Endpoint | Scope | What it does |
| --- | --- | --- | --- |
| Live | `GET /v1/accounts` | `accounts:read` | List trading accounts, newest first. |
| Live | `GET /v1/accounts/{account}` | `accounts:read` | One account with live balance, equity and open positions. |
| Planned | `POST /v1/accounts` | `accounts:write` | Provision one or more accounts. Spends account credits. |
| Live | `PUT /v1/accounts/{account}/trader` | `accounts:write` | Assign an account to a trader by name and email, creating the trader if needed. |
| Live | `DELETE /v1/accounts/{account}/trader` | `accounts:write` | Take an account back off its holder. Revokes its sessions. |
| Planned | `POST /v1/accounts/{account}/archive` | `accounts:write` | Take an account out of circulation and revoke its sessions. |
| Live | `GET /v1/accounts/{account}/trades` | `accounts:read` | Full trade history, open and closed, cursor-paginated. |
| Live | `GET /v1/accounts/{account}/positions` | `accounts:read` | Open positions only, marked to the latest price. |
| Live | `GET /v1/accounts/{account}/orders` | `accounts:read` | Resting orders — limits and stops that have not triggered. |
| Live | `GET /v1/accounts/{account}/rule-flags` | `accounts:read` | Rules the account tripped that flag rather than breach. |
| Planned | `POST /v1/accounts/{account}/breach` | `trading:write` | Force a breach for a violation caught outside the platform. |
| Planned | `POST /v1/accounts/{account}/positions` | `trading:write` | Open a position on an account's behalf. |
| Planned | `DELETE /v1/accounts/{account}/positions/{id}` | `trading:write` | Close an open position. |

## Credentials

|  | Endpoint | Scope | What it does |
| --- | --- | --- | --- |
| Live | `GET /v1/accounts/{account}/credentials` | `credentials:read` | The Account Number / Server / Password a trader signs in with. |
| Live | `POST /v1/accounts/{account}/credentials/rotate` | `credentials:read` | Generate a new password and end every live session. |

## Traders

|  | Endpoint | Scope | What it does |
| --- | --- | --- | --- |
| Planned | `GET /v1/traders` | `accounts:read` | List trader records. |
| Planned | `POST /v1/traders` | `accounts:write` | Create a trader record. Sends nothing and grants no login. |
| Planned | `GET /v1/traders/{id}` | `accounts:read` | One trader with the accounts they hold. |
| Planned | `PATCH /v1/traders/{id}` | `accounts:write` | Correct a trader's name or email. |
| Planned | `GET /v1/traders/{id}/summary` | `accounts:read` | Aggregate performance across every account this trader holds. |

## Account types

|  | Endpoint | Scope | What it does |
| --- | --- | --- | --- |
| Planned | `GET /v1/account-types` | `accounts:read` | The challenges you sell, with their balances and rule sets. |
| Planned | `POST /v1/account-types` | `accounts:write` | Create a challenge tier pointing at a rule set. |
| Planned | `PATCH /v1/account-types/{id}` | `accounts:write` | Rename or reprice a challenge. Existing accounts are unaffected. |

## Rule sets

|  | Endpoint | Scope | What it does |
| --- | --- | --- | --- |
| Planned | `GET /v1/rule-sets` | `accounts:read` | List rule sets and their current version. |
| Planned | `POST /v1/rule-sets` | `accounts:write` | Create a rule set. |
| Planned | `GET /v1/rule-sets/{id}/versions` | `accounts:read` | Every version of a rule set, newest first. |
| Planned | `POST /v1/rule-sets/{id}/versions` | `accounts:write` | Append a new version. Never edits an existing one. |
| Planned | `GET /v1/rule-catalog` | `accounts:read` | Every rule you can build with, and which your tier unlocks. |

## Analytics

|  | Endpoint | Scope | What it does |
| --- | --- | --- | --- |
| Live | `GET /v1/accounts/{account}/analytics` | `accounts:read` | One account's whole performance picture in a single response. |
| Live | `GET /v1/accounts/{account}/equity` | `accounts:read` | The balance and equity series behind the chart. |
| Live | `GET /v1/accounts/{account}/report` | `accounts:read` | The whole account in one response — deals and evaluation terms included. |

## Reporting

|  | Endpoint | Scope | What it does |
| --- | --- | --- | --- |
| Planned | `GET /v1/reports/overview` | `accounts:read` | Firm-wide counts, pass rate and net P&L across every account. |
| Planned | `GET /v1/reports/leaderboard` | `accounts:read` | Accounts ranked by gain, drawdown or profit factor. |
| Planned | `GET /v1/reports/decisions` | `accounts:read` | A feed of breaches and passes, newest first. |

## Market data

|  | Endpoint | Scope | What it does |
| --- | --- | --- | --- |
| Planned | `GET /v1/instruments` | `accounts:read` | Tradeable symbols with their asset class and leverage. |
| Planned | `GET /v1/prices` | `accounts:read` | Latest bid/ask for one or more instruments. |
| Planned | `GET /v1/candles` | `accounts:read` | OHLC history for an instrument, back to 2000 where we have it. |

## Billing

|  | Endpoint | Scope | What it does |
| --- | --- | --- | --- |
| Planned | `GET /v1/credits` | `accounts:read` | Granted, used and remaining account credits, with your tier. |
| Planned | `GET /v1/credits/ledger` | `accounts:read` | Every grant and debit that moved your balance. |
| Planned | `GET /v1/purchases` | `accounts:read` | Your plan, pack and upgrade purchases. |

## Branding

|  | Endpoint | Scope | What it does |
| --- | --- | --- | --- |
| Planned | `GET /v1/branding` | `accounts:read` | How your executor currently looks. |
| Planned | `PUT /v1/branding` | `accounts:write` | Update logos, colours and the login screen. Tier-gated. |

## Webhooks

|  | Endpoint | Scope | What it does |
| --- | --- | --- | --- |
| Planned | `GET /v1/webhooks` | `accounts:read` | List your webhook endpoints. |
| Planned | `POST /v1/webhooks` | `accounts:write` | Register a URL and get its signing secret. |
| Planned | `DELETE /v1/webhooks/{id}` | `accounts:write` | Stop delivering to an endpoint. |
| Planned | `GET /v1/webhooks/{id}/deliveries` | `accounts:read` | Delivery attempts and their outcomes, for debugging. |
| Planned | `POST /v1/webhooks/{id}/test` | `accounts:write` | Send a signed test event so you can verify your handler. |

## API keys

|  | Endpoint | Scope | What it does |
| --- | --- | --- | --- |
| Live | `GET /api-keys` | `panel` | List your organization's API keys. |
| Live | `POST /api-keys` | `panel` | Issue a key. The token is returned once. |
| Live | `POST /api-keys/{id}/revoke` | `panel` | Revoke a key immediately. |

## Webhook events

Every event below is a decision the platform already makes internally — the rule engine writes a breach before it returns, and the executor is already told over an internal bus. Delivering them to you is a fan-out of existing behaviour, not new behaviour.

|  | Event |
| --- | --- |
| Planned | `account.breached` |
| Planned | `account.passed` |
| Planned | `account.flagged` |
| Planned | `account.created` |
| Planned | `account.assigned` |
| Planned | `account.archived` |
| Planned | `position.opened` |
| Planned | `position.closed` |
| Planned | `order.rejected` |
| Planned | `credits.low` |

## Need one sooner

Priority follows what customers actually block on. If a planned endpoint is what stands between you and shipping, say so — [book a call](https://www.propexecutor.com/consultation) or email [info@propexecutor.com](mailto:info@propexecutor.com) — and it moves up.

# What is not available

> Payouts, trader logins, commission, KYC — the boundaries of this platform, and what to do instead.

Source: https://www.propexecutor.com/docs/not-available
Whole reference in one file: https://www.propexecutor.com/doc.md

Things a prop firm might reasonably look for here and will not find. None of these are oversights or roadmap items we are being coy about — each one is a deliberate boundary, and knowing where it sits will save you building against something that is not coming.

## Payouts and trader payments

There is no endpoint to charge a trader for a challenge, and none to pay a trader their split. No `/payouts`, no `/challenge-purchases`, no split-payment plumbing.

- **Why:** money moving between your firm and your traders is your relationship, your licensing position and your liability. Putting it through us would make us a party to it.
- **What to do instead:** run it in your own stack. Use `account_types.price_cents` to record what you charge so your dashboard can display it, and [analytics](https://www.propexecutor.com/docs/analytics) to compute what a passing trader is owed. The numbers are all here; the transaction is yours.

This existed once, as a trader storefront with self-service checkout, and was removed when the scope narrowed. It is not returning without a deliberate decision.

## Trader logins, KYC and documents

No trader authentication, no password reset, no trader dashboard, no document upload, no KYC status field.

A [trader](https://www.propexecutor.com/docs/concepts#trader) here is a record — a name and an email — and the only thing that logs into the executor is a set of [account credentials](https://www.propexecutor.com/docs/credentials), which name an account rather than a person. Whoever holds the password trades that account; we never model them as a user. Identity, onboarding and compliance live in your systems.

## Commission, swap and financing

The simulator charges none of them, so `profit_total.loss_commission` and `profit_total.profit_swap` are always `0`. If your challenge economics assume trading costs, they are not reflected in any number this API returns.

> **Worth planning around**
> This is the gap most likely to matter to you, because it makes a simulated account slightly more generous than a live one. If you need modelled costs, tell us — it is a change to how fills are priced, not a field we can add.

## Also not modelled

| Not available | Why |
| --- | --- |
| Multi-currency accounts | Every account is USD. `currency` is reported as a constant rather than omitted. |
| Order source (EA vs manual vs signal) | Nothing records how an order arrived, so `profit_type` reports everything as `manual`. |
| Time-based rules | News blackouts and inactivity timeouts need a scheduler, which would put the breach decision behind a timer. The [catalogue](https://www.propexecutor.com/docs/rule-sets#catalog) lists them as unavailable and the API refuses them. |
| Changing a live account's rules | Versions are frozen at creation. See [why](https://www.propexecutor.com/docs/rule-sets#append-only). |
| Refunding a credit | A breached or archived account does not return its credit. That is what you paid for when it was created. |
| Real order routing | Fills are simulated against live prices. Nothing reaches a broker, an exchange or any venue, ever. |
| Affiliate and discount codes | Part of selling challenges, which happens in your stack. |

## If one of these is a blocker

Some of the above are boundaries we will not cross; others are simply not built. The difference matters to you, so ask rather than guess — [book a call](https://www.propexecutor.com/consultation) and we will tell you plainly which is which.
