A GET, PATCH or DELETE is safe to retry: the second attempt lands on the same record and repeats the same effect, or answers 404. With If-Match, a retried PATCH or DELETE whose first attempt went through can answer 412, because your own write changed the tag. Re-read the record before you decide it failed.
A POST is not safe to retry. When the response to a POST gets lost and you retry, you can create a second row.
An Idempotency-Key header closes that gap. Every POST accepts one.
In the examples, <your-url> is the address you sign in to plnnrly on, such as acme.plnnrly.com, or your organisation's own domain.
Send a key
Pick a new key for each write you mean to make once: a UUID or a ULID works. Store it with the work you are about to do, before you send the request. A retry must send the same key as the attempt whose answer you never saw, so a key you generate on the retry path protects nothing.
curl -X POST "https://<your-url>/api/v2/worklogs" \
-H "Authorization: Bearer <token>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f1c9a52-7d4e-4b8a-9c21-5e6f0a7b8d13" \
-d '{
"project_id": 12,
"date": "2026-10-14",
"minutes": 90,
"description": "Pairing on the importer"
}'A key is at most 255 characters. Spaces around it are trimmed, and an empty key counts as no key. A longer key is 422 validation-failed, with an error for parameter Idempotency-Key and code max.
What a retry gets back
Retry with the same key and the same body, and you get the first response back: the same status and the same body, with its Content-Type, Location and ETag headers. One header is added:
Idempotency-Replayed: true
The first, real write does not carry it. So Idempotency-Replayed tells you "this already happened" without comparing bodies. X-Request-Id is new on every replay, because each attempt is a request of its own.
How long and for whom a key counts
- A stored response is kept for 24 hours. After that, the same key writes again.
- Only successes are stored: responses below
400. After a4xxor5xxthe key is free again, so you can correct the request and retry with the same key. - While the first request is still running, a retry with the same key answers
409idempotency-key-in-flight withRetry-After: 1. That claim lasts at most 120 seconds. - A key belongs to one organisation and one token. The same key sent with another token is another key. For an app signed in with OAuth, the key belongs to the app and the user, so it survives a refresh of the access token.
- A key is bound to the path and the JSON body it was first used with. The order of keys and the whitespace in the body do not matter. The same key with a different body, or on another path, is
409idempotency-key-reused.
Where a key is required
These operations refuse to run without a key, because a lost 201 retried without one would report a failure, or write twice, for a write that had in fact succeeded:
| Operation | Why |
|---|---|
| POST /worklogs | A duplicate worklog counts the same hours twice. |
| POST /work-items/{work_item}/attachments | A retry without a key fails on the spent upload ticket, although the file exists. |
| POST /drive-items/{drive_item}/files | The same as for attachments. |
Without a key, these answer 400 idempotency-key-required. Everywhere else the key is optional, and each operation page says which of the two it is.
When it goes wrong
| Status | Problem type | Cause | What to do |
|---|---|---|---|
409 | idempotency-key-reused | The key was used before with a different body or path. | Use a new key for a new write. |
409 | idempotency-key-in-flight | The first request with this key has not finished. | Wait for Retry-After, then retry with the same key. |
400 | idempotency-key-required | The operation requires a key and you sent none. | Send one. |
idempotency-key-in-flight means your retry overtook the first attempt, not that anything failed. Retry with the same key. A new key would create the duplicate the key exists to prevent.
Over MCP
AI clients write through the api_write tool, which neither requires nor honours an Idempotency-Key. A POST /worklogs needs no key there, and repeating a call writes again. See MCP tools.
Idempotency is not concurrency
An idempotency key stops a POST from being applied twice. It does not stop a PATCH from overwriting someone else's change: that is If-Match, a different header with a different failure. See Concurrency and ETags.
Related
- Concurrency and ETags:
If-MatchandIf-None-Match. - Errors and retries: every problem type.
- Files: upload and download: the two registrations that require a key.
- Limits at a glance: every numeric limit in one table.