API reference
Idempotency
How to make a POST safe to retry: the key, the 24-hour replay window, what counts as the same request, and what to do after a timeout.
Updated
Send Idempotency-Key on every POST. Without it, a request that times out leaves you with no way to know whether the payment exists, and the only options are to charge twice or not at all.
curl https://cleared-pay.com/api/v1/payments \
-H "Authorization: Bearer $CLEARED_SECRET_KEY" \
-H "Idempotency-Key: 4f0a2c6e-9b31-4c0d-9a7e-1f2b3c4d5e6f" \
-H "Content-Type: application/json" \
-d '{"amount":2000,"currency":"eur","reference":"ORDER-10432","payment_method":{"type":"card","token":"tok_01JBXQ2M4K7T9V3ZC8HR5N6PWD"}}'The rules
| You send | You get |
|---|---|
| A new key | The request runs. Its response is stored for 24 hours. |
| The same key, the same request, within 24 hours | The stored response, byte for byte, with Idempotent-Replayed: true. |
| The same key, a different request | 409 idempotency_error, code idempotency_key_conflict. Nothing runs. |
| The same key, while the first is still in flight | Your request waits up to 5 seconds for the first one, then answers with its result. If it is still running, 409 idempotency_key_in_use with Retry-After: 1. |
| The same key after 24 hours | A fresh request. The key is free again. |
A replay returns whatever the original returned — including a 402 decline, a 422 failure and a 500. That is the point: the retry answers the question "what happened", not "try again".
Keys are at most 255 characters and scoped to your merchant and environment. A UUID per logical operation is the right choice; a timestamp or a counter is not, because it changes when your process restarts mid-retry.
What counts as the same request
The comparison is over the HTTP method, the path, and a canonical form of the JSON body in which object keys are sorted and undefined values are dropped. So key order and whitespace do not matter, and a changed amount does.
The path is part of it, so the same key on POST /v1/payments and on POST /v1/payments/:id/refunds is a conflict, not two operations. Use one key per operation.
Which endpoints
Every POST honours the header. Four deliberately do not, because they are inherently repeatable and a stored replay would hide the result you asked for:
POST /v1/sandbox/tokens— a token is single-use by designPOST /v1/sandbox/simulate-webhook— you want the new delivery attempt, not the old onePOST /v1/events/:id/resend— same reasonPOST /v1/sandbox/advance/:paymentId— advancing twice is not meaningful
GET and DELETE are idempotent by nature and ignore the header.
Retrying correctly
- Generate the key before the first attempt and keep it with the operation, not with the attempt.
- Retry on a timeout, a connection error, a
5xxor a429— with the same key, and with backoff. - Do not retry a
400,402,404or422. The answer will not change. - Never regenerate the key on retry. A new key is a new payment.
const key = crypto.randomUUID(); // once per order, stored with the order
async function createPayment(body: unknown) {
for (let attempt = 0; attempt < 4; attempt++) {
const res = await fetch("https://cleared-pay.com/api/v1/payments", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLEARED_SECRET_KEY}`,
"Idempotency-Key": key, // the same key on every attempt
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (res.status < 500 && res.status !== 429) return res; // final answer, decline included
const retryAfter = Number(res.headers.get("retry-after") ?? 0);
await new Promise((r) => setTimeout(r, Math.max(retryAfter * 1000, 2 ** attempt * 250)));
}
throw new Error("Cleared-pay did not answer; the Idempotency-Key is safe to reuse.");
}If you have lost the key and need to know whether a payment happened, look it up by your own reference:
curl -G https://cleared-pay.com/api/v1/payments \
-H "Authorization: Bearer $CLEARED_SECRET_KEY" \
-d "reference=ORDER-10432"reference is yours and is not enforced unique, so this is a recovery path, not a substitute for the key.
What idempotency does not cover
It makes a request safe to repeat. It does not make an operation safe to repeat with a new key: two payments created with two keys are two payments, whatever the reference says. Deduplicating at your own boundary — one key per order, stored before the first attempt — is the part only you can do.