Idempotency
Idempotency lets you safely retry a POST request — after a network blip or
a timeout — without risking a duplicate side effect. Send an Idempotency-Key
and Evinor guarantees the operation runs at most once for that key.
Idempotency applies to POST requests only. Other methods are already
naturally idempotent (GET) or address a specific resource (DELETE,
PATCH), so they ignore the header.
Sending an idempotency key
Add an Idempotency-Key header with a unique value you generate — a UUID is a
good choice:
curl -X POST https://api.evinor.ai/v1/sensors \
-H "Authorization: Bearer evnr_live_your_key_here" \
-H "Idempotency-Key: 3f1c9a2e-6b4d-4e2a-9f77-0c53412fd0e1" \
-H "Content-Type: application/json" \
-d '{ "name": "…", "filter": { … }, "actions": [ … ] }'
The key must be 1–255 characters from A-Za-z0-9_-. A key outside that
charset or length returns a 400
validation-failed.
How it behaves
Keys are scoped to your API key. For a given key, Evinor tracks the request and its outcome:
| Situation | Result |
|---|---|
| First use of the key | The request executes normally and its response is stored. |
| Replay — same key, same request, after it completed | The stored response is returned verbatim, with an Idempotency-Replayed: true header. |
| Conflict — same key, a different request | 422 idempotency-key-conflict. |
| In progress — same key, original request still running | 409 idempotency-in-flight. |
"Same request" means the same HTTP method, path, and request body. Reusing a key with any of those changed is treated as a conflict, so give each distinct operation its own key.
Important details
- Validation failures are never cached. If a first attempt fails request
validation (
400), the key is not consumed — fix the payload and retry with the same key. - Replays don't re-run the operation. A replay returns the original stored outcome; it never creates a second resource or fires a second side effect.
- Signing secrets are not replayed. When you create a sensor or rotate a
secret with an
Idempotency-Key, the one-timesigning_secretis present only in the original response. It is omitted from the stored copy, so a replay of the same key returns the sensor withoutsigning_secret. Capture the secret from the first response — see Getting Started. - A stuck request self-heals. If an original request never finished (for example, the caller crashed mid-flight), the key becomes reclaimable after a short window so a later retry with the same key can proceed. This does not apply to billed endpoints — see below.
Billed endpoints
A billed endpoint charges your credit balance when it runs. Today that is a new
event search — POST /v1/events/search without a
cursor. Because a charge cannot be taken back, idempotency there is
at-most-once, and one rule differs from everything above:
- A crashed billed request is not reclaimable. If the original request never
reported its outcome, retrying with the same key returns
409idempotency-in-flightinstead of executing — and keeps doing so. Evinor cannot tell "crashed before it charged" apart from "charged, then crashed", so it refuses to risk a second charge. Retry with a freshIdempotency-Key. - Everywhere else, the stale claim is reclaimed after roughly a minute and the
same key proceeds. A retry loop written for that behavior — retry the same key
on a
409, forever — will wedge on a billed endpoint. - A
402releases the key. Aninsufficient-creditsrefusal happens before anything is charged, so the claim is released: top up and retry the identical request with the same key and it executes. - A
502retains the key — deliberately. On anupstream-errorEvinor doesn't know whether the charge committed, so the key stays claimed and a same-key retry returns409instead of executing. That refusal is the protection: it is what stops the retry becoming a second charge. Retry at most once, under the same key, and treat a repeating502as a support question — not as something to loop on with fresh keys, since each fresh key is a new, separately-billable execution. - Replays are never charged again. A completed billed request replays its
stored response — same
execution_id,Idempotency-Replayed: true— without re-running or re-charging. - Continuation pages are not billed and follow the general rules: a crashed page can be retried with the same key.
- Keys do not cross transports. The same search issued over REST and over
the MCP server is two different requests, so one key used for both
yields a
422idempotency-key-conflictrather than a replay. Give each transport its own keys.
Recommended pattern
- Generate one idempotency key per logical operation (e.g. per "create this sensor" intent), and reuse it across retries of that operation.
- Retry on network errors, timeouts,
409,429, and503using the same key — except on a billed endpoint, where a409after a crash means "start over with a fresh key", not "wait and retry". - On success or on a replay, record the result and stop retrying.