Docs

Type a word or a question. Esc closes the search.

DE
Sign in Start free
Developers/Basics

Retry a write, run it once.

Send an Idempotency-Key with a POST or PATCH. If the connection drops and you send the same request again, up4 returns the first answer instead of creating a second monitor.

Use the header

KEY=$(uuidgen)

curl -si -X POST https://app.upfour.io/v1/monitors \
  -H "Authorization: Bearer $UP4_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d '{"name":"Example","url":"https://example.com","monitor_type":"http"}' \
  | grep -i -E '^(HTTP|idempotency-status)'
HTTP/2 201
idempotency-status: stored

Run the same command again with the same KEY:

HTTP/2 201
idempotency-status: replayed

The rules

TopicBehavior
HeaderIdempotency-Key, any non-empty text. A UUID is a good choice. An empty or missing header turns the feature off for that request.
MethodsPOST and PATCH. PUT, DELETE and GET ignore the header.
Not coveredPOST /monitors/batch ignores this layer; it requires the header and keeps its own receipt (same key and body: the stored receipt, a different body: a refusal).
RequiredPOST /incidents/{incidentID}/escalate and POST /escalation-policies/{policyID}/test answer 400 without the header and accept at most 128 characters. Everywhere else it is optional and has no length limit.
A key belongs toYour Company, your user, the method and path of the request (not the query string), and the key text. The same key on another path is a different key.
Body comparisonJSON bodies are compared as JSON, so whitespace and key order do not matter. Other bodies are compared byte for byte.
Stored for24 hours after the first response, then deleted. After that the key is free again: the next request with it runs and its response is stored anew.
What is storedResponses with a status from 200 to 499 and a JSON body, including validation errors. Not stored: 5xx and responses without a body.
SecretsThe response is stored as sent. A replay of POST /webhook-subscriptions or POST /webhook-subscriptions/{id}/rotate-secret therefore carries the same one-time secret again within those 24 hours, also after a later rotation made it obsolete. See Get the signing secret.
ReplaysReturn the stored status, content type and body with Idempotency-Status: replayed.
Rate limitsA replay counts as a request.

What you get

SituationResult
No receipt for the keyThe request runs. A response with status 200 to 499 and a JSON body is stored and carries Idempotency-Status: stored.
Receipt, same bodyThe stored status and body come back with Idempotency-Status: replayed. The request does not run again.
Receipt, different body409 idempotency_key_conflict. details.original_request_at holds the time of the first request.
Receipt, but made with another API key or under changed access409 idempotency_replay_unavailable. The request does not run again.
Response was 5xx, 204 or had no JSON bodyNothing is stored. A retry with the same key runs the request again.
{
  "error": {
    "code": "idempotency_key_conflict",
    "message": "Idempotency key was already used with a different request body",
    "request_id": "req_…",
    "details": { "original_request_at": "2026-10-27T09:14:02Z" }
  }
}

The receipt remembers who sent the request: the API key, its scopes and access level, and its client access. A retry with another key, or after one of those changed, gets idempotency_replay_unavailable instead of a replay, and the request is not run again. Look at the resource to see what happened.

Common mistakes

  • A new key on every retry. Generate the key once per intended operation and reuse it for every attempt of that operation.
  • One key for different operations. The second body gets 409 idempotency_key_conflict. Use one key per operation.
  • Fixing a rejected request under the same key. A 400 is stored too. Resending the same body replays the 400; resending a corrected body is a conflict. Use a new key for the corrected request.
  • Retrying while the first request is still running. The receipt is written when the first request finishes, so an overlapping retry is not guaranteed to be caught. Wait for the response or your timeout.
  • Switching to another API key for the retry. See idempotency_replay_unavailable above.
  • Relying on it for DELETE or PUT. The header is ignored on those methods.