Idempotency
Endpoints that create or move value — creating tickets, repaying loans, triggering direct debits — require an idempotency key so a retried request can never double-apply:
X-Idempotency-KeyrequiredA unique, client-generated key, at most 256 characters. The first successful response is cached and replayed for 24 hours on any retry carrying the same key.
curl -X POST https://api.getroja.com/v1/tickets \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 9f4a2c1e-7b3d-4e8f-a1b2-c3d4e5f60789" \
-d '{ "type": "lend", "principal_amount": 100000, "currency": "NGN", ... }'
Choosing keys
- Generate a UUID per logical operation, not per HTTP attempt. The whole point is that retries of the same operation share a key.
- Persist the key with the pending operation (in your job queue, client state, etc.) so a crash-and-restart still retries with the same key.
- Don't reuse keys across different operations. Within the 24-hour window you'd get the first operation's cached response back.
Retry pattern
const key = crypto.randomUUID(); // one key for this operation
async function createTicket(body, attempt = 1) {
const res = await fetch("https://api.getroja.com/v1/tickets", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
"X-Idempotency-Key": key, // same key on every attempt
},
body: JSON.stringify(body),
});
if (res.status >= 500 && attempt < 4) {
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
return createTicket(body, attempt + 1);
}
return res.json();
}
Which endpoints require the header is marked in the
API reference — look for X-Idempotency-Key in the
endpoint's header parameters.