Skip to main content
When you record a decision, the network can fail after MeshQu has persisted it but before your code receives the response. Retrying blindly would create a duplicate decision. An idempotency key closes that gap: retry as many times as you like and you get back the same decision, recorded once.
Mental model: the idempotency key is a stable name for a business operation. The first request with that key records a decision; every later request with the same key returns the decision that already exists.
Evaluate vs Record — Idempotency applies to record, the endpoint that persists a decision. evaluate records no decision, so it has no decision to deduplicate. It is not a no-op write, though: resolving the active policies may append one row to the tenant’s append-only policy_snapshots table, returned as policy_snapshot_id. That append is content-addressed — a repeat call resolving to the same policies reuses the existing snapshot and appends nothing.

How it works

When you call POST /v1/decisions/record with an idempotency_key, MeshQu checks whether a decision already exists with that key for your tenant: The second case returns the stored decision without running policies again, so retries are both safe and cheap.
A key is bound permanently to the first request recorded under it. Sending a different request under a key that is already in use is refused with 409 and the error code IDEMPOTENCY_KEY_REUSED — MeshQu will not hand you a receipt for content you did not submit, and will not silently record your request under someone else’s key.The 409 body tells you which of the two things to do:
Retrying the same mismatched pair will fail the same way — this is not a transient error.

What counts as “the same content”

The comparison is over the decision context: decision_type, fields, evidence, source_artifact, and metadata. Values the server writes into the context for you are excluded, so an ordinary network retry can never conflict:
  • metadata.correlation_id — regenerated per request when you send no X-Correlation-Id header.
  • The agent_licence_* metadata keys — resolved server-side from your API key, so a re-issued licence does not conflict.
Request inputs that live outside the context are not compared at all: evidence_manifest, evidence_manifest_digest, action and options.chain. Changing only one of those under an existing key returns the stored decision (200) rather than a 409. The 409 body publishes this list as fingerprint_ignores.

Recording with an idempotency key

The TypeScript SDK requires idempotency_key on record() and throws if it is missing or blank. The REST API treats it as optional but strongly recommended — a record call without a key still succeeds, it just won’t be deduplicated. For any workflow that can retry (which is most of them), always supply one.

Choosing good keys

A key should uniquely and deterministically identify the business operation, so that a retry of the same operation produces the same key. Good keys — derived from a stable business identifier:
Bad keys — collide across operations, or change on every retry (defeating the purpose):

Key format

A key, once used, is bound to that decision permanently within the tenant. There is no expiry, so a retry months later still returns the original decision. If a decision genuinely needs to be re-made (for example after corrected input data), use a new key with a version suffix — this is enforced, not merely advised: reusing the old key with the corrected data is refused with 409.

Safe-retry pattern

Treat both 200 and 201 as success. Retry only on server errors (5xx) and network failures — never on client errors (4xx), which indicate a request that won’t succeed on retry. 409 IDEMPOTENCY_KEY_REUSED is one of those: it means the key is already bound to different content, so retrying unchanged will fail identically. Choose a new key, or restore the original request body.
Because the key is stable, a retry after a post-write network failure returns the already-recorded decision instead of creating a second one.

Common patterns

Queue processing — use the message ID so reprocessing a redelivered message is a no-op:
Inbound webhooks — key on the delivery ID of the upstream system so its retries are absorbed:
Environment prefixes — avoid cross-environment collisions if a non-production system can ever reach the same tenant:

Verifying a key was used

Recorded decisions carry their idempotency_key. List recent decisions and filter: