curl --request GET \
--url https://api.pay.aptahq.com/v1/collections/{reference} \
--header 'X-AptaPay-Signature: <api-key>'const options = {method: 'GET', headers: {'X-AptaPay-Signature': '<api-key>'}};
fetch('https://api.pay.aptahq.com/v1/collections/{reference}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.pay.aptahq.com/v1/collections/{reference}"
headers = {"X-AptaPay-Signature": "<api-key>"}
response = requests.get(url, 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": {}
}
}Poll a collection's status
Ask whether a charge has actually succeeded yet.
By default this reads the gateway’s own ledger — a database read, not a provider call — so polling it is cheap and safe. It reports the status the gateway last WROTE, which moves when the provider’s webhook lands or when reconciliation next polls upstream.
Pass live=true when a customer is watching a payment screen. The gateway then asks the underlying provider what it currently holds and applies the answer before responding, so a charge the customer just approved on their handset is reported as successful on that very poll rather than on whichever later poll happens to follow the webhook or a reconciliation rung. Without it, a customer can be left waiting a minute or more after their money has already moved.
A live check never changes the response shape and never fails the read: an unreachable provider, a transaction the provider cannot look up, an already-terminal record, or a caller inside the per-reference cooldown all return the stored record with a 200. The only observable difference is that the status may be fresher. Upstream calls are collapsed to at most one per reference per few seconds however many callers ask, so it is safe to poll on a timer and from several tabs at once.
Branch on status. pending means keep waiting, requires_action means the customer still owes an OTP or a redirect, successful and failed are terminal. Never treat the 201 from the charge itself as settlement.
Your webhook handler remains the primary signal and the source of truth for settlement; live=true is what keeps a waiting customer informed in the meantime.
A 404 means “no such transaction FOR THIS TENANT” — another tenant’s reference is deliberately indistinguishable from one that never existed, so this endpoint cannot be used to probe whether a reference belongs to somebody else.
curl --request GET \
--url https://api.pay.aptahq.com/v1/collections/{reference} \
--header 'X-AptaPay-Signature: <api-key>'const options = {method: 'GET', headers: {'X-AptaPay-Signature': '<api-key>'}};
fetch('https://api.pay.aptahq.com/v1/collections/{reference}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.pay.aptahq.com/v1/collections/{reference}"
headers = {"X-AptaPay-Signature": "<api-key>"}
response = requests.get(url, 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": {}
}
}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.
Path Parameters
Query Parameters
Set to the exact string true to force an upstream status check before responding. Any other value, or omitting it, returns the stored record. Use it on a customer-facing payment screen; omit it for receipts, dashboards and bulk reads, which do not need sub-second freshness and should not cause a provider call.
true Response
The transaction.
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