Idempotency
Some writes have a real-world effect that must not happen twice — triggering a payroll distribution, for
instance. Those operations require an Idempotency-Key, and the API guarantees that replaying the same key
produces the original response instead of a second effect.
The API reference marks which operations require the header.
Sending the key
curl -X POST "https://<external-api-host>/api/v1/client/app/payroll/distribution/trigger" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Idempotency-Key: 6f1e8c94-3d5a-4c2b-9f7e-11a2b3c4d5e6" \
-H "Content-Type: application/json" \
-d '{ … }'
Generate a fresh key — a UUID is ideal — for each distinct business intent, and reuse that same key for
every retry of that intent. Omitting the header on an operation that requires one is a 400
(idempotency/key-required).
The body and the response below are captured from the test suite, not transcribed — what you see is what the API received and returned.
{
"credentialsLogin": "partner-payroll-login",
"companySiret": "12345678901234",
"payrollPeriod": "2026-01-01"
}
{
"isSuccess": true,
"errorMessage": null,
"distributionUrl": "https://staffngo.example.com/payroll/distribution/sample"
}
What identifies a duplicate
Three things together: your partner identity, the key, and a hash of the request body, scoped to the exact capability and version you called.
Two consequences worth knowing:
- Reusing a key with a different body is a different request, not a replay. Never recycle keys across unrelated calls.
- The same key against a different endpoint, or a different version of the same endpoint, is also distinct — you will never be served a response shaped for another contract.
What you get back
| Situation | Response |
|---|---|
| First call | The handler runs; its response is returned and memorized. |
| Replay after completion | The memorized response, verbatim — same status, same body. No second execution. |
| Replay while the first is still running | 409 with type = idempotency/in-progress. Wait and retry. |
| First call failed (non-2xx) or crashed | The key is released. Retrying with the same key runs the operation again. |
That last row is the one to design around: only successful writes are memorized. A failed attempt does not poison the key, so a legitimate retry after an error is not silently swallowed.
Practical guidance
- Persist the key alongside the operation you are attempting, before sending it. A key you regenerate on retry offers no protection.
- On a
409 idempotency/in-progress, back off and retry the same key rather than issuing a new one — a new key would create a second effect, which is exactly what you were avoiding. - On a timeout with no response, retry with the same key. That is the case idempotency exists for: you do not know whether the write landed, and the API does.
Legacy tokens
Calls authenticated with a deprecated BearerClient token bypass this mechanism entirely — a replay is not
deduplicated. It is one more reason to migrate to OAuth credentials, see
Authentication.