Skip to main content

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:

SituationResult
First use of the keyThe request executes normally and its response is stored.
Replay — same key, same request, after it completedThe stored response is returned verbatim, with an Idempotency-Replayed: true header.
Conflict — same key, a different request422 idempotency-key-conflict.
In progress — same key, original request still running409 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-time signing_secret is present only in the original response. It is omitted from the stored copy, so a replay of the same key returns the sensor without signing_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 409 idempotency-in-flight instead 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 fresh Idempotency-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 402 releases the key. An insufficient-credits refusal 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 502 retains the key — deliberately. On an upstream-error Evinor doesn't know whether the charge committed, so the key stays claimed and a same-key retry returns 409 instead 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 repeating 502 as 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 422 idempotency-key-conflict rather than a replay. Give each transport its own keys.
  1. Generate one idempotency key per logical operation (e.g. per "create this sensor" intent), and reuse it across retries of that operation.
  2. Retry on network errors, timeouts, 409, 429, and 503 using the same key — except on a billed endpoint, where a 409 after a crash means "start over with a fresh key", not "wait and retry".
  3. On success or on a replay, record the result and stop retrying.