Skip to main content
Send an Idempotency-Key header on every money-moving call. AptaPay claims the key before making any provider call, so a network failure mid-request can never leave you unable to tell whether money moved.
Use any unpredictable unique string. A UUID with dashes stripped is fine.

What the key guarantees

The second row is the one that protects you. A key is bound to a fingerprint of the request — the amount, the destination, the currency and the other money-determining fields. If those differ, it is not a retry, so we refuse it rather than guessing which one you meant.
Never mint a new idempotency key to get past a retriable or ambiguous error. A new key restarts the whole operation as if it were unrelated — which is how one charge becomes two. Retry with the same key, always.

Keys are scoped to your tenant

Your keys are yours. Another tenant using the same string cannot collide with you, and cannot probe whether one of your keys exists.

Choosing a key

Derive it from the operation in your own system, not from the clock:
idempotency.ts
If you generate a random key, generate it once, store it alongside the operation, and reuse it for every retry of that operation.

Recovering from an unknown outcome

Because the key is claimed before the provider is contacted, “no record” genuinely means the provider was never called. That makes recovery deterministic:
1

Retry with the same key

If nothing moved, you get a fresh attempt. If it did move, you get the original response replayed rather than a second charge.
2

If the error is ambiguous, poll instead

Call GET /v1/collections/{reference} or GET /v1/payouts/{reference} until it reaches a terminal status. Do not reverse. See Errors.

Where it applies

Every money-moving endpoint: POST /v1/collections, POST /v1/payouts, and POST /v1/fx/exchange. Quotes and reads do not need a key, though sending one is harmless.