Idempotency
Retry Vacation Tracker API v2 writes safely with the Idempotency-Key header.
Networks fail. A write can time out on your side after Vacation Tracker has already applied it. An Idempotency-Key lets you retry that write without creating a second leave, a second label, or a second anything.
The Idempotency-Key header
Send Idempotency-Key on any write request (POST, PUT, PATCH, DELETE). The header is optional, but we recommend it on every write.
curl -X POST https://api.vacationtracker.io/v2/leaves \
-H "Authorization: Bearer vt_live_your-secret" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f1c7a52-3d0e-4b8e-9d6a-2c4f0b9e1a77" \
-d '{ "userId": "...", "leaveTypeId": "...", "startDate": "2026-10-05", "endDate": "2026-10-09" }'If the request fails or times out, send it again with the same header value and the same body.
Key format
- 1 to 255 printable ASCII characters (no spaces or control characters).
- Use a random UUID (version 4) for each logical write, for example
5f1c7a52-3d0e-4b8e-9d6a-2c4f0b9e1a77. - Create a new key for each new write. Reuse a key only to retry the same write.
A header that breaks these rules is rejected with 400 INVALID_IDEMPOTENCY_KEY and nothing is written.
Scope and retention
- Per API key. Keys are stored per API key. Two different API keys can send the same
Idempotency-Keyvalue without affecting each other. - 24 hours. A key is kept for 24 hours after the first request that used it. After that, the same value counts as a new key and the write runs again.
What counts as the same request
Vacation Tracker stores a fingerprint of the first request that used a key. Two requests match when all of these are equal:
- the HTTP method,
- the endpoint and its path parameters (for example the leave ID in
/v2/leaves/{id}/approve), - the JSON body (key order and whitespace do not matter),
- the user in the
X-On-Behalf-Ofheader, if you send one.
Other headers do not count.
Behaviour on retry
| Situation | Response |
|---|---|
| First request with this key | The write runs normally. |
| Same key, same request, first request finished | The original response is returned again: same status code, same body. It carries an extra Idempotent-Replayed: true header. The write does not run again. |
| Same key, same request, first request still running | 202 Accepted with the operation of the first request. Poll it as described in Synchronous writes and operations. The write does not run again. |
| Same key, different request | 422 IDEMPOTENCY_KEY_REUSED. Nothing is written. |
The original response is replayed as is
The replay returns exactly what the first request returned:
- A
201or200returns the same resource as it was at that time. Read the resource again if you need its current state. - A
202returns the same operation. Itsstatusin the replayed body may be out of date, so poll the operation URL for the current status. - A
4xxerror (for example a leave rejected by a blackout period) is replayed too. Fix the request and send it with a new key.
Two cases are not stored, so a retry with the same key runs the write:
- Requests rejected before the key is checked, for example
INSUFFICIENT_SCOPEorON_BEHALF_OF_NOT_ALLOWED. - Requests that failed with
500 INTERNAL_ERRORbefore the write was submitted.
If a write was submitted and then something unexpected happened, the key is kept and the response is a 202 with the operation, so a retry never submits the write twice.
Reused key with a different payload
{
"error": {
"code": "IDEMPOTENCY_KEY_REUSED",
"name": "IdempotencyKeyReused",
"description": "This Idempotency-Key was already used with a different request payload.",
"resolution": "Use a new Idempotency-Key for a different request, or resend the original payload.",
"requestId": "..."
}
}The status is 422. This check comes first, so you get it even while the first request is still running.
Concurrent retries
If you send the same request again while the first one is still running, the retry does not wait and does not run the write. It returns 202 Accepted right away:
{
"operation": {
"id": "pa_...",
"status": "PENDING",
"url": "/v2/operations/pa_..."
}
}In the rare case where the first request has not linked its operation yet, the retry returns 409 IDEMPOTENCY_REQUEST_IN_PROGRESS. Wait a few seconds and retry with the same key.
Errors
| Status | Code | When |
|---|---|---|
400 | INVALID_IDEMPOTENCY_KEY | The header is not 1 to 255 printable ASCII characters. |
409 | IDEMPOTENCY_REQUEST_IN_PROGRESS | A request with this key is still being processed. |
422 | IDEMPOTENCY_KEY_REUSED | The key was already used with a different request. |