Payouts
The Payout endpoints let a strategy request simulated payouts from a funded prop-firm challenge account, check whether a payout is currently allowed, and list past payouts. Like the Replay Control endpoints, they are a TestMax extension not present in the real TopStep ProjectX Gateway.
Payout rules
Eligibility follows TopStep-style rules (configurable per challenge, with these defaults):
- At least 5 winning days of $150+ closed profit each since the last payout (the current, unfinished sim day never counts)
- Positive net profit since the last payout
- Payout amount between the minimum ($125) and the maximum for the account size, capped at 50% of profits
- On consistency-policy challenges, no single day may exceed the consistency percentage of window profit
POST Payout/eligibility
Check whether a funded account can request a payout right now, and what the current limits are.
URL: POST /api/topstep-sim/Payout/eligibility
Authentication: Bearer token required.
Request body
{ "accountId": 12345}| Field | Type | Required | Description |
|---|---|---|---|
accountId | number | Yes | A funded challenge account ID |
Response
{ "success": true, "errorCode": 0, "errorMessage": null, "eligibility": { "eligible": true, "reasons": [], "winningDays": 6, "requiredWinningDays": 5, "winningDayMinProfit": 150, "netProfitSincePayout": 2400.00, "totalFundedProfits": 3100.00, "consistencyPct": null, "consistencyLimit": 0, "maxPayout": 1200.00, "minPayout": 125, "windowStartDate": null }}| Field | Type | Description |
|---|---|---|
eligibility.eligible | boolean | Whether a payout can be requested right now |
eligibility.reasons | array | Human-readable reasons when not eligible |
eligibility.winningDays | number | Qualifying winning days since the last payout |
eligibility.requiredWinningDays | number | Winning days required |
eligibility.winningDayMinProfit | number | Minimum closed profit for a day to count as winning |
eligibility.netProfitSincePayout | number | Net profit since the last payout (or funded start) |
eligibility.totalFundedProfits | number | Total profit since the funded phase began |
eligibility.consistencyPct | number or null | Consistency percentage rule, or null on standard-policy challenges |
eligibility.consistencyLimit | number | Best-day cap implied by the consistency rule |
eligibility.maxPayout | number | Largest amount you can request right now |
eligibility.minPayout | number | Smallest amount you can request |
eligibility.windowStartDate | string or null | Sim date of the last payout (window lower bound) |
Error responses
| Scenario | errorCode | errorMessage |
|---|---|---|
Invalid or unowned accountId | 1 | "Account not found" |
| Account is not funded, or no longer active | 2 | "Account is not in funded phase" |
POST Payout/request
Request a payout of a specific amount. On success the amount is deducted from the account balance and recorded in the payout history.
URL: POST /api/topstep-sim/Payout/request
Authentication: Bearer token required.
Request body
{ "accountId": 12345, "amount": 500.00}| Field | Type | Required | Description |
|---|---|---|---|
accountId | number | Yes | A funded challenge account ID |
amount | number | Yes | Payout amount in USD. Must be a positive number within the eligibility limits. |
Response
{ "success": true, "errorCode": 0, "errorMessage": null, "payoutId": "c1a2b3d4-..."}| Field | Type | Description |
|---|---|---|
payoutId | string | ID of the recorded payout |
Error responses
| Scenario | errorCode | errorMessage |
|---|---|---|
Invalid or unowned accountId | 1 | "Account not found" |
| Account is not funded, or no longer active | 2 | "Account is not in funded phase" |
Missing, non-numeric, or out-of-range amount | 3 | "Invalid payout amount" |
| Eligibility rules not met | 4 | "Payout not eligible..." — the response also includes the full eligibility object |
| Concurrent request or commit failure | 5 | "Payout failed — please retry" |
Example
# Check eligibility, then request the maximum allowedelig = api("Payout/eligibility", {"accountId": ACCOUNT_ID})
if elig["success"] and elig["eligibility"]["eligible"]: amount = elig["eligibility"]["maxPayout"] result = api("Payout/request", {"accountId": ACCOUNT_ID, "amount": amount}) if result["success"]: print(f"Payout of ${amount:,.2f} recorded: {result['payoutId']}")else: for reason in elig["eligibility"]["reasons"]: print(f"Not eligible: {reason}")POST Payout/history
List all payouts for a challenge account, newest first.
URL: POST /api/topstep-sim/Payout/history
Authentication: Bearer token required.
Request body
{ "accountId": 12345}| Field | Type | Required | Description |
|---|---|---|---|
accountId | number | Yes | A challenge account ID (funded or not) |
Response
{ "success": true, "errorCode": 0, "errorMessage": null, "payouts": [ { "id": "c1a2b3d4-...", "amount": 500.00, "balanceBefore": 52400.00, "balanceAfter": 51900.00, "status": "completed", "requestedAt": "2025-01-20T15:04:05.000Z" } ]}| Field | Type | Description |
|---|---|---|
payouts | array | Payout records, newest first |
payouts[].id | string | Payout ID |
payouts[].amount | number | Amount paid out |
payouts[].balanceBefore | number | Account balance before the payout |
payouts[].balanceAfter | number | Account balance after the payout |
payouts[].status | string | Payout status (e.g., "completed") |
payouts[].requestedAt | string | ISO 8601 timestamp of the request |