curl --request POST \
--url https://api.pay.aptahq.com/v1/refunds \
--header 'Content-Type: application/json' \
--header 'X-AptaPay-Signature: <api-key>' \
--data '
{
"original_reference": "<string>",
"amount_minor": 123,
"reason": "<string>"
}
'const options = {
method: 'POST',
headers: {'X-AptaPay-Signature': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({original_reference: '<string>', amount_minor: 123, reason: '<string>'})
};
fetch('https://api.pay.aptahq.com/v1/refunds', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.pay.aptahq.com/v1/refunds"
payload = {
"original_reference": "<string>",
"amount_minor": 123,
"reason": "<string>"
}
headers = {
"X-AptaPay-Signature": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}{
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}{
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}{
"code": 501,
"message": "The routed provider does not support this operation.",
"error": {
"code": "provider_capability_unsupported",
"details": {
"capability": "refunds",
"reason": "refund_orchestration_not_implemented"
}
}
}Reversals — currently a hard 501, always
Not implemented. This endpoint ALWAYS returns 501, for every provider, regardless of routing.
Do not build against this expecting it to route through to a provider: no adapter is called, no original transaction is loaded, and no float moves. details.reason is always refund_orchestration_not_implemented — a statement about the GATEWAY’s own safety posture, not about which provider you are routed to. This is deliberately stricter than “the routed provider has no refund endpoint”: even a provider that DID support refunds would still get this 501, because the gateway itself has no safe orchestration for the money-affecting invariants a real refund needs — its own idempotent reference, ownership verification against the original transaction, a bound on cumulative refunds against what was collected, and a float debit committed in the same transaction as the claim.
Check GET /v1/capabilities if you want the per-provider reason string instead (e.g. “Eversend exposes no refund endpoint”) — that describes provider capability, which is a DIFFERENT and narrower statement than this route’s unconditional refusal.
curl --request POST \
--url https://api.pay.aptahq.com/v1/refunds \
--header 'Content-Type: application/json' \
--header 'X-AptaPay-Signature: <api-key>' \
--data '
{
"original_reference": "<string>",
"amount_minor": 123,
"reason": "<string>"
}
'const options = {
method: 'POST',
headers: {'X-AptaPay-Signature': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({original_reference: '<string>', amount_minor: 123, reason: '<string>'})
};
fetch('https://api.pay.aptahq.com/v1/refunds', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.pay.aptahq.com/v1/refunds"
payload = {
"original_reference": "<string>",
"amount_minor": 123,
"reason": "<string>"
}
headers = {
"X-AptaPay-Signature": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}{
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}{
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}{
"code": 501,
"message": "The routed provider does not support this operation.",
"error": {
"code": "provider_capability_unsupported",
"details": {
"capability": "refunds",
"reason": "refund_orchestration_not_implemented"
}
}
}Authorizations
v1={hex HMAC-SHA256} over the ten-field canonical string. Sent alongside X-AptaPay-Key, X-AptaPay-Timestamp and X-AptaPay-Nonce — all four are required. OpenAPI can only model one header per scheme, so the other three are described in the Authentication section above.
Body
The gateway reference of the original collection.
UGX, RWF, XAF, XOF, KES, GHS, NGN, TZS, ZMW, USD, EUR, GBP Partial refund amount, minor units. Omitted for a full refund. IGNORED today — the request never reaches an adapter.
Free-text reason. IGNORED today.
Response
Validation failed.
The single response envelope, used by EVERY endpoint so a consuming app parses one shape everywhere.
200
"OK"
Present on success. Shape varies per endpoint.
Machine-readable error classification. EXACTLY ONE of terminal/retriable/ ambiguous is true. laces_api inferred this from the HTTP status and got it wrong, stranding real money twice (2026-08-15, 2026-08-16). Branch on these booleans, never on the status code:
terminal -> the provider refused. Reverse the debit and tell the user. retriable -> safe to retry with the SAME Idempotency-Key. ambiguous -> the outcome is UNKNOWN. Do NOT reverse. Poll instead.
Show child attributes
Show child attributes