Idempotency
The API supports idempotency so you can repeat mutations safely without executing the same operation twice. The classic case: a POST /api/v1/charges drops due to a network error and you don't know whether the charge was created — resend with the same key and the original response comes back, without creating a second charge.
How to use it
Send the header on any POST, PUT, or PATCH:
X-Idempotency-Key: <key>
The key is generated on your side — we recommend UUID v4. Maximum of 255 characters.
curl -i \
-H "Authorization: Bearer $DUNNING_TOKEN" \
-H 'Content-Type: application/json' \
-H 'X-Idempotency-Key: 8c0f5d6e-3f8b-4cb5-9a47-d8f5b15e9b21' \
-d '{ "personId": "cmd8k1x2y0009cd4e", "amount": 1500.00, "dueDate": "2026-08-10" }' \
-X POST 'https://api.dunning.kobana.com.br/api/v1/charges'
How it works
- The key is reserved atomically, scoped to your organization (different organizations can use the same key without conflict).
- The server computes the request's fingerprint: a hash of
method + path + raw body. - The handler runs and the response (status, body, headers) is recorded against the key — including when it's an error.
- A new request with the same key within 24 hours receives the original response verbatim, with the extra header:
X-Idempotent-Replay: true
After 24 hours the key expires and the same key produces a fresh execution.
Conflicts
| Scenario | Status | code |
|---|---|---|
| Same key with a different method/path/body | 409 | IDEMPOTENCY_KEY_REUSED |
| Another request with the same key in flight | 409 | IDEMPOTENCY_REQUEST_IN_PROGRESS |
| Empty key or more than 255 characters | 400 | VALIDATION_ERROR |
Example conflict response:
{
"message": "X-Idempotency-Key já foi usado com parâmetros diferentes. Reenvie os parâmetros originais ou gere uma nova chave",
"code": "IDEMPOTENCY_KEY_REUSED"
}
On IDEMPOTENCY_REQUEST_IN_PROGRESS, wait a moment and resend the same request: either the first execution finishes and you get the replay, or the reservation was released and your attempt runs.
When the result is not recorded
It's safe to resend when the handler never got to run:
- The request was rejected before the handler (invalid JSON, missing authentication).
- The handler threw an exception — the reservation is released so the next retry runs clean.
Accepted methods
| Method | Accepts X-Idempotency-Key? |
|---|---|
POST / PUT / PATCH | ✅ Yes |
GET / DELETE | ❌ No — already idempotent by definition; the header is ignored |
Best practices
- One key per business operation; repeat it only on retries of the same operation.
- Persist the key alongside the operation's state on your side — a post-crash retry uses the same key.
- Combine with exponential backoff on
5xx/timeout: the key guarantees the retry duplicates nothing. - Since errors are recorded too, an error replay is a signal to fix the request and generate a new key — not to insist on the same one.
- Discard keys older than 24 hours on your side; the server has already expired them.