If a write is cut off halfway (the network drops, your process restarts), you don’t know whether watsi applied it. The Idempotency-Key header lets you retry it safely: if the first one was already applied, the retry returns its result instead of writing again.
curl -X POST "https://api.watsi.ai/v1/actions/contacts/create" \
-H "Authorization: Bearer $WATSI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed" \
-d '{ "name": "María Pérez", "phone_number": "5215598765432" }'Which routes accept it
The writes that are not idempotent by nature. Reads, and writes that leave the same state however many times you repeat them (such as setting a conversation’s status), ignore it because they are already safe to retry. This list is generated from the contract:
| Method | Path | Scopes | Idempotency-Key |
|---|---|---|---|
POST | /v1/actions/agents/create | agents:write | Yes |
POST | /v1/actions/agents/knowledge | knowledge:write | Yes |
POST | /v1/actions/agents/knowledge/delete | knowledge:write | Yes |
POST | /v1/actions/agents/update | agents:write | Yes |
POST | /v1/actions/attachments/create-upload | agents:write | Yes |
POST | /v1/actions/attachments/finalize | agents:write | Yes |
POST | /v1/actions/attachments/import-url | agents:write | Yes |
POST | /v1/actions/contacts/create | contacts:write | Yes |
POST | /v1/actions/crm/tags | crm:write | Yes |
POST | /v1/actions/crm/tags/remove | crm:write | Yes |
POST | /v1/actions/lifecycle/attach-file | lifecycle:write | Yes |
POST | /v1/actions/voice/config | agents:write | Yes |
How it behaves
| Case | Response |
|---|---|
| First request with a key | Runs normally |
| Same key, same body, within 30 seconds | 200 with the first one’s stored response; nothing is written |
| Same key, different body, within 30 seconds | 409 IDEMPOTENCY_KEY_REUSED |
| Same key after 30 seconds | Treated as a new request |
| The first request failed | Nothing was stored: the retry runs again |
- A key is scoped per workspace and per route: the same key on
contacts/createand oncrm/tagsare two different keys. - The body comparison is by content: the order of JSON properties does not matter.
- The 30-second window is meant for the immediate retry of a request that was cut off, not for deduplicating jobs that span hours. For that, keep your own record of which operations you completed.
Good practice
- One key per logical operation, generated before the first attempt (a UUID v4 works) and reused on every retry of that operation.
- Never the same key for two different operations: the second body gets a
409. - If you get
409 IDEMPOTENCY_KEY_REUSED, it is a bug in your code (two operations shared a key), not something a retry fixes.
contacts/create is also an upsert: sending the same phone, wa_id or email updates the existing contact instead of duplicating it, even without an Idempotency-Key and outside the window. The response says so with "created": false.