Trading accounts
List, read, provision, assign and archive the trading accounts your firm runs.
A trading account is one simulated account running on your executor. It is created against an account type, freezes that type’s rule set version at creation, and is identified by an 8-digit number your trader signs in with.
List accounts
/v1/accountsAvailablescope · accounts:readNewest first, cursor-paginated.
curl "https://api.propexecutor.com/v1/accounts?limit=50" \
-H "Authorization: Bearer $PFX_KEY"Each row
iduuidaccountintegeraccount_type_iduuidaccount_type_namestring$10K Standard.trader_iduuid | nulltrader_emailstring | nullstatusstringactive, breached, passed or archived.status_reasonstring | nullstatus_changed_attimestamp | nullstarting_balancenumbercreated_attimestampRead one account
/v1/accounts/{account}Availablescope · accounts:readThe same row plus live trading state — balance, equity and open positions as of now.
{
"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
/v1/accountsPlannedscope · accounts:writeCreates one or more accounts against an account type. Up to 100 per request.
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}'account_type_iduuid · requiredquantityinteger1, capped at 100 per request.trader_iduuidThis spends credits, permanently
Each account costs one account credit for good. Breaching or archiving it gives nothing back. Send an Idempotency-Key so a network retry cannot spend a second batch, and see limits for the 402 you get when you run out.
Assign a trader
/v1/accounts/{account}/traderAvailablescope · accounts:writeHands 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 -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"}'namestringemailstringJordan@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.{
"account": 10000042,
"trader": {
"id": "3d21f0c2-…",
"name": "Jordan Ellis",
"email": "jordan@example.com"
},
"trader_created": true,
"assigned": true,
"sessions_revoked": true
}trader_createdbooleanassignedbooleansessions_revokedbooleanAn 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.
{
"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
/v1/accounts/{account}/traderAvailablescope · accounts:writeTakes the account back off whoever holds it. No body — the account number is the whole request.
curl -X DELETE https://api.propexecutor.com/v1/accounts/10000042/trader \
-H "Authorization: Bearer $PFX_KEY"{
"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 — 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
/v1/accounts/{account}/archivePlannedscope · accounts:writeTakes the account out of circulation and revokes its sessions. The record, its trades and its history stay readable. No credit is returned.
Traders
/v1/tradersPlannedscope · accounts:read/v1/tradersPlannedscope · accounts:writeA 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.
You rarely need these: assigning an account 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 -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
/v1/accounts/{account}/tradesAvailablescope · accounts:readEvery position the account has ever taken, open and closed, newest first, cursor-paginated. An open position has closed_at and realized_pnl of null.
iduuidticketintegerid to join.instrumentstringEURUSD, XAUUSD, BTCUSD.sidestringlong or short.sizenumberentry_pricenumberexit_pricenumber | nullopened_attimestampclosed_attimestamp | nullrealized_pnlnumber | nullPaginated 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 — it computes the totals server-side rather than making you sum pages.
Open positions
/v1/accounts/{account}/positionsAvailablescope · accounts:readWhat 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.
{
"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
}floating_pnlnumbermark_pricenumbermargin_usednumberstop_lossnumber | nullThis 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 instead: that is the ledger, and it is exact.
Resting orders
/v1/accounts/{account}/ordersAvailablescope · accounts:readLimits and stops that have been placed and have not triggered. Same live, in-memory source as positions, and the same caveat.
order_typestringtrigger_pricenumberrequired_marginnumberstatusstringRule flags
/v1/accounts/{account}/rule-flagsAvailablescope · accounts:readRules 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.
{
"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: a flag is not a breach, and the two are reported separately on purpose.