Skip to content
Back to App

Simulated Withdrawals

The Payout/* endpoints let a strategy request simulated withdrawals from a challenge account in the performance phase, check whether a withdrawal is currently allowed, and list past withdrawals. Like the Replay Control endpoints, they are a TestMax extension to the gateway schema.

Withdrawal rules

Eligibility follows the preset rules (configurable per challenge, with these defaults):

  • At least 5 winning days of $150+ closed profit each since the last withdrawal (the current, unfinished sim day never counts)
  • Positive net profit since the last withdrawal
  • Withdrawal 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 performance-phase account can request a withdrawal right now, and what the current limits are.

URL: POST /api/sim/Payout/eligibility

Authentication: Bearer token required.

Request body

{
"accountId": 12345
}
FieldTypeRequiredDescription
accountIdnumberYesA performance-phase 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 withdrawal can be requested right now
eligibility.reasonsarrayHuman-readable reasons when not eligible
eligibility.winningDaysnumberQualifying winning days since the last withdrawal
eligibility.requiredWinningDaysnumberWinning days required
eligibility.winningDayMinProfitnumberMinimum closed profit for a day to count as winning
eligibility.netProfitSincePayoutnumberNet profit since the last withdrawal (or performance-phase start)
eligibility.totalFundedProfitsnumberTotal profit since the performance 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 withdrawal (window lower bound)

Error responses

ScenarioerrorCodeerrorMessage
Invalid or unowned accountId1"Account not found"
Account is not in the performance phase, or no longer active2"Account is not in the performance phase"

POST Payout/request

Request a simulated withdrawal of a specific amount. On success the amount is deducted from the account balance and recorded in the withdrawal history.

URL: POST /api/sim/Payout/request

Authentication: Bearer token required.

Request body

{
"accountId": 12345,
"amount": 500.00
}
FieldTypeRequiredDescription
accountIdnumberYesA performance-phase challenge account ID
amountnumberYesWithdrawal amount in USD (simulated). Must be a positive number within the eligibility limits.

Response

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

Error responses

ScenarioerrorCodeerrorMessage
Invalid or unowned accountId1"Account not found"
Account is not in the performance phase, or no longer active2"Account is not in the performance phase"
Missing, non-numeric, or out-of-range amount3"Invalid withdrawal amount"
Eligibility rules not met4"Withdrawal not eligible..." — the response also includes the full eligibility object
Concurrent request or commit failure5"Withdrawal 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"Withdrawal of ${amount:,.2f} recorded: {result['payoutId']}")
else:
for reason in elig["eligibility"]["reasons"]:
print(f"Not eligible: {reason}")

POST Payout/history

List all simulated withdrawals for a challenge account, newest first.

URL: POST /api/sim/Payout/history

Authentication: Bearer token required.

Request body

{
"accountId": 12345
}
FieldTypeRequiredDescription
accountIdnumberYesA challenge account ID (performance phase 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
payoutsarrayWithdrawal records, newest first
payouts[].idstringWithdrawal ID
payouts[].amountnumberAmount withdrawn (simulated)
payouts[].balanceBeforenumberAccount balance before the withdrawal
payouts[].balanceAfternumberAccount balance after the withdrawal
payouts[].statusstringWithdrawal status (e.g., "completed")
payouts[].requestedAtstringISO 8601 timestamp of the request