Reverse a payout (restore the withdrawn balance)
Reverse a payout that was cancelled after the funds were withdrawn: the payout amount is credited back to the account and the payout is marked reversed in the ledger. One call — no manual balance edits.
What a reversal does to the account (mirror of the payout):
- Adds the payout
amountback to the balance. The high-water mark and the daily baseline move up by the same amount, so the credit never appears as trading profit, Day P&L, or reduced drawdown room. - Decrements
payoutCountby one (maxPayoutscounts only non-reversed payouts from now on). - Payout cycle: when the payout started the current consistency cycle (
consistencyReset) or a later payout did, the cycle starting balance moves up by the amount too, so the credit does not count as cycle profit. When the payout happened inside the current cycle, the restored balance restores that cycle’s profit exactly. - A loss floor locked by the payout (
moveMllToLock) is left unchanged and returned aslossFloorBalance; adjust it yourself if the cancellation should unlock it.
Rules:
- The account must be flat (no open positions or working orders) and tradable (
not_started/in_progress). Passed, failed, expired and turned-off (inactive) accounts are refused. - Any payout of the account can be reversed, not only the latest; the cycle handling above applies to each.
- A payout can be reversed once — a second attempt returns 409
PAYOUT_ALREADY_REVERSED. The credit is applied atomically with the ledger update, so it can never be applied twice. - Reversed payouts stay in
GET /accounts/{accountId}/payoutswithstatus: "reversed"and are excluded from payout activity totals.
Idempotency (recommended): send an Idempotency-Key (or x-idempotency-key) header. A replay with the same key returns the original reversal (200, duplicate: true) and never credits twice. After a 502 REVERSAL_NOT_CONFIRMED, retry with the same key: the retry either confirms the earlier reversal or completes it. 4xx answers are cached per key, so after fixing the cause (e.g. flattening the account) retry with a new key.
Webhooks: account.payout_reversed (with a payout block: amount, balances before/after the reversal, new payout count, original payout details). Subscribers of account.balance_changed also receive the balance credit.
Example:
curl -X POST ".../v1/organization/accounts/7c9e6679-7425-40de-944b-e07fc1f90ae7/payouts/e5f6a7b8-c9d0-1234-ef01-234567890abc/reverse" \
-H "X-API-Key: hp_liv...ere" \
-H "Idempotency-Key: reverse-PO-1042" \
-H "Content-Type: application/json" \
-d '{ "reason": "Payout PO-1042 cancelled by finance" }'
Error codes:
| Code | Meaning |
|---|---|
ACCOUNT_NOT_FOUND / PAYOUT_NOT_FOUND | Not in your organization / not a payout of this account (404) |
PAYOUT_ALREADY_REVERSED | The payout was already reversed (409) |
OPEN_EXPOSURE | Account has open positions/working orders — flatten first (409) |
ACCOUNT_NOT_TRADABLE | Account is passed/failed/expired/turned off (409) |
PAYOUT_REVERSAL_IN_PROGRESS | Another request is reversing this payout right now (409) |
ENGINE_UNAVAILABLE | The reversal could not be applied — NOT reversed; retry (502) |
REVERSAL_NOT_CONFIRMED | The outcome could not be confirmed — retry with the same idempotency key; never applied twice (502) |
Permissions: requires manage access to “Payouts”.
Authentication: send your organization API key in the X-API-Key header.
curl --request POST \
--url https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"reason": "Payout PO-1042 cancelled by finance — funds returned to account"
}
'import requests
url = "https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse"
payload = { "reason": "Payout PO-1042 cancelled by finance — funds returned to account" }
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({reason: 'Payout PO-1042 cancelled by finance — funds returned to account'})
};
fetch('https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'reason' => 'Payout PO-1042 cancelled by finance — funds returned to account'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse"
payload := strings.NewReader("{\n \"reason\": \"Payout PO-1042 cancelled by finance — funds returned to account\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"reason\": \"Payout PO-1042 cancelled by finance — funds returned to account\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"reason\": \"Payout PO-1042 cancelled by finance — funds returned to account\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"message": "Payout reversed — balance restored",
"data": {
"payoutId": "e5f6a7b8-c9d0-1234-ef01-234567890abc",
"accountId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"amount": 1600,
"status": "reversed",
"balanceBefore": 50900,
"balanceAfter": 52500,
"payoutCount": 0,
"cycleStartingBalanceShifted": true,
"lossFloorBalance": 50100,
"reason": "Payout PO-1042 cancelled by finance",
"reversedAt": "2026-10-09T14:05:00.000Z",
"duplicate": false
}
}{
"success": false,
"statusCode": 401,
"error": "Unauthorized",
"message": "Authentication required",
"code": "UNAUTHORIZED"
}{
"success": false,
"statusCode": 403,
"error": "Forbidden",
"message": "Access denied",
"code": "FORBIDDEN"
}{
"statusCode": 404,
"error": "Not Found",
"message": "Payout not found on this account",
"code": "PAYOUT_NOT_FOUND"
}{
"success": false,
"code": "PAYOUT_ALREADY_REVERSED",
"message": "This payout has already been reversed"
}{
"success": false,
"statusCode": 500,
"error": "Internal Server Error",
"message": "An unexpected error occurred",
"code": "INTERNAL_ERROR"
}{
"success": false,
"code": "REVERSAL_NOT_CONFIRMED",
"message": "The reversal could not be confirmed. Retry with the same Idempotency-Key — it is never applied twice."
}Authorizations
Organization API key. Format: "hp_live_{key}". Organization admins manage the key in the dashboard.
Path Parameters
Trading account the payout was withdrawn from
Payout to reverse (id from POST/GET /accounts/{accountId}/payouts)
Body
Why the payout is being reversed — stored on the payout, in the audit trail, and in the webhook event.
3 - 500"Payout PO-1042 cancelled by finance — funds returned to account"
Response
Payout reversed
true
"Payout reversed — balance restored"
Show child attributes
Show child attributes
{
"payoutId": "e5f6a7b8-c9d0-1234-ef01-234567890abc",
"accountId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"amount": 1600,
"status": "reversed",
"balanceBefore": 50900,
"balanceAfter": 52500,
"payoutCount": 0,
"cycleStartingBalanceShifted": true,
"lossFloorBalance": 50100,
"reason": "Payout PO-1042 cancelled by finance",
"reversedAt": "2026-10-09T14:05:00.000Z",
"duplicate": false
}
curl --request POST \
--url https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"reason": "Payout PO-1042 cancelled by finance — funds returned to account"
}
'import requests
url = "https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse"
payload = { "reason": "Payout PO-1042 cancelled by finance — funds returned to account" }
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({reason: 'Payout PO-1042 cancelled by finance — funds returned to account'})
};
fetch('https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'reason' => 'Payout PO-1042 cancelled by finance — funds returned to account'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse"
payload := strings.NewReader("{\n \"reason\": \"Payout PO-1042 cancelled by finance — funds returned to account\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"reason\": \"Payout PO-1042 cancelled by finance — funds returned to account\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hyperprop.com/platform/v1/organization/accounts/{accountId}/payouts/{payoutId}/reverse")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"reason\": \"Payout PO-1042 cancelled by finance — funds returned to account\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"message": "Payout reversed — balance restored",
"data": {
"payoutId": "e5f6a7b8-c9d0-1234-ef01-234567890abc",
"accountId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"amount": 1600,
"status": "reversed",
"balanceBefore": 50900,
"balanceAfter": 52500,
"payoutCount": 0,
"cycleStartingBalanceShifted": true,
"lossFloorBalance": 50100,
"reason": "Payout PO-1042 cancelled by finance",
"reversedAt": "2026-10-09T14:05:00.000Z",
"duplicate": false
}
}{
"success": false,
"statusCode": 401,
"error": "Unauthorized",
"message": "Authentication required",
"code": "UNAUTHORIZED"
}{
"success": false,
"statusCode": 403,
"error": "Forbidden",
"message": "Access denied",
"code": "FORBIDDEN"
}{
"statusCode": 404,
"error": "Not Found",
"message": "Payout not found on this account",
"code": "PAYOUT_NOT_FOUND"
}{
"success": false,
"code": "PAYOUT_ALREADY_REVERSED",
"message": "This payout has already been reversed"
}{
"success": false,
"statusCode": 500,
"error": "Internal Server Error",
"message": "An unexpected error occurred",
"code": "INTERNAL_ERROR"
}{
"success": false,
"code": "REVERSAL_NOT_CONFIRMED",
"message": "The reversal could not be confirmed. Retry with the same Idempotency-Key — it is never applied twice."
}