Skip to content
Back to App

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
}
FieldTypeRequiredDescription
accountIdnumberYesA 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
}
}
FieldTypeDescription
eligibility.eligiblebooleanWhether a payout can be requested right now
eligibility.reasonsarrayHuman-readable reasons when not eligible
eligibility.winningDaysnumberQualifying winning days since the last payout
eligibility.requiredWinningDaysnumberWinning days required
eligibility.winningDayMinProfitnumberMinimum closed profit for a day to count as winning
eligibility.netProfitSincePayoutnumberNet profit since the last payout (or funded start)
eligibility.totalFundedProfitsnumberTotal profit since the funded phase began
eligibility.consistencyPctnumber or nullConsistency percentage rule, or null on standard-policy challenges
eligibility.consistencyLimitnumberBest-day cap implied by the consistency rule
eligibility.maxPayoutnumberLargest amount you can request right now
eligibility.minPayoutnumberSmallest amount you can request
eligibility.windowStartDatestring or nullSim date of the last payout (window lower bound)

Error responses

ScenarioerrorCodeerrorMessage
Invalid or unowned accountId1"Account not found"
Account is not funded, or no longer active2"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
}
FieldTypeRequiredDescription
accountIdnumberYesA funded challenge account ID
amountnumberYesPayout amount in USD. Must be a positive number within the eligibility limits.

Response

{
"success": true,
"errorCode": 0,
"errorMessage": null,
"payoutId": "c1a2b3d4-..."
}
FieldTypeDescription
payoutIdstringID of the recorded payout

Error responses

ScenarioerrorCodeerrorMessage
Invalid or unowned accountId1"Account not found"
Account is not funded, or no longer active2"Account is not in funded phase"
Missing, non-numeric, or out-of-range amount3"Invalid payout amount"
Eligibility rules not met4"Payout not eligible..." — the response also includes the full eligibility object
Concurrent request or commit failure5"Payout failed — please retry"

Example

# Check eligibility, then request the maximum allowed
elig = 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
}
FieldTypeRequiredDescription
accountIdnumberYesA 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"
}
]
}
FieldTypeDescription
payoutsarrayPayout records, newest first
payouts[].idstringPayout ID
payouts[].amountnumberAmount paid out
payouts[].balanceBeforenumberAccount balance before the payout
payouts[].balanceAfternumberAccount balance after the payout
payouts[].statusstringPayout status (e.g., "completed")
payouts[].requestedAtstringISO 8601 timestamp of the request