Concepts

Idempotency keys

POST /prospects, POST /suppressions and POST /hooks accept an optional Idempotency-Key header. With a key, you can retry a request after a timeout and be sure it runs once.

curl -X POST "https://app.anymail.watch/api/v1/prospects" \
  -H "Authorization: Bearer $ANYMAIL_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5f1c0e1a-2b7d-4a4e-9c1a-0f1e2d3c4b5a" \
  -d '{"email":"ada@example.com"}'

The key is a string of 1 to 255 characters. A UUID v4 is a good choice.

What happens#

Situation Result
First request with the key It runs as usual. The status and JSON body are stored for the workspace for 24 hours.
Same key, same method, same path with query, same body byte for byte The stored status and body return without running the request. The response has Idempotent-Replayed: true.
Same key, a different request 422 with {"error":"Idempotency-Key was already used with a different request."}
Same key while the first request is still running 409 with {"error":"A request with this Idempotency-Key is still being processed."}. Retry shortly.
Empty key, or longer than 255 characters 400 with {"error":"Idempotency-Key must be between 1 and 255 characters."}

Details that matter#

  • The API stores 2xx and 4xx responses. A 409 duplicate is stored too, so a replay returns what the first call returned.
  • The API does not store 5xx responses. After a server fault, a retry with the same key runs again.
  • The key belongs to the workspace. Two tokens of one workspace share one key space.
  • The API compares the body as raw bytes. The same JSON with the keys in another order is a different request and returns 422. Resend an identical payload.
  • A replay uses a request of your rate limit.
  • Without the header, nothing changes. A repeated POST /prospects without a key returns 409 with code: "duplicate".

Endpoints that ignore the header#

PATCH /prospects/{id} and DELETE /hooks/{id} ignore the header, because repeating them gives the same result. The same applies to POST /campaigns/{id}/pause, POST /campaigns/{id}/resume, PATCH /replies/{id} and DELETE /suppressions/{id}. A repeated POST /campaigns/{id}/prospects returns already-enrolled for each person who is already enrolled.