Retries and idempotency
A network can fail after Kosoor has saved your request but before you read the answer. Retrying then must not create a second customer or invoice. Every POST and PATCH therefore carries an Idempotency-Key header, and a retry sends the same key.
curl https://api.kosoor.com.sa/v1/customers \
-H "Authorization: Bearer ksr_live_..." \
-H "Idempotency-Key: 9b2c4c0e-6a51-4c0b-8a43-3c2f2f1a7d10" \
-H "Content-Type: application/json" \
-d '{"type":"person","firstName":"Sara","phone":"+966501234567"}'
The key
- 1 to 255 printable ASCII characters, matched case-sensitively. A UUID per logical operation works well.
- Keys belong to your API key: another key may use the same value without colliding.
- Kosoor keeps a key for 30 days.
What a retry gets
| You send | Kosoor answers |
|---|---|
| the same key with the same method, path and body, after a success | the stored reply, with Idempotent-Replayed: true; nothing runs again |
| the same key with a different request | 422 idempotency_key_reused |
| the same key while the first request is still running | 409 idempotency_in_progress with Retry-After: 1 |
| the same key after a failure | the request runs again: only successful replies are stored |
| no key | 400 missing_idempotency_key |
A request that died mid-way holds its key for 5 minutes; after that a retry with the same key runs it.
When to retry
Retry with the same key on a timeout, a dropped connection, 409 idempotency_in_progress and any 5xx. Do not retry a 4xx unchanged: it will answer the same way. Fix the request and send it under a new key. Three refusals depend on time instead, and are retried later with the same key, since no 4xx reply is stored: 409 invoice_not_cleared, once ZATCA has cleared the invoice; 409 invoice_busy, after a few seconds; and 422 loyalty_refund_blocked, once, a few minutes later. If it is refused again, the merchant issues the credit note from the dashboard, where a supervisor may override.