Skip to content
Beta. This surface may change before GA; breaking changes are dated in the changelog.

Idempotency and retries

Network calls fail, and a retry must not create a second of anything. Nicia splits writes into two groups, and you only have to think about one of them.

When you name the thing you are writing, the address is the identity. Sending the same request twice is one write, and there is nothing to configure.

Operation Why it is safe
PUT /v1/brains/{handle} Your handle is the identity
PATCH / PUT …/records/{id} on a record that EXISTS The id is the identity
POST …/documents with an id An upsert on your document id

This is why the documents page recommends sending your own id and the records page recommends deriving slugs from your own system: it turns a nightly sync into a plain loop with no bookkeeping.

The one gap the address cannot close is a create whose answer you never received. A retry re-reads the address, finds nothing there — because the first attempt may have failed, or may have staged for review — and writes again. Nicia will not paper over that by hashing your request into a key: two creates you meant separately are byte-identical to a retry, so a derived key would answer the first one’s outcome to the second and drop a write while reporting success. Send a key on a create instead; every record write verb accepts one.

Non-addressed writes take an Idempotency-Key

Section titled “Non-addressed writes take an Idempotency-Key”

When the server assigns the identity, a retry cannot be told apart from a deliberate repeat — so you say which it is.

Terminal window
curl -X POST "https://api.nicia.ai/v1/brains/tenant:acme/batch" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-H "idempotency-key: sync:acme:2026-08-14" \
-d '{ "records": [ … ] }'
Operation Without a key
POST …/documents without an id At-least-once — may create two documents
POST …/batch At-least-once
POST …/keys At-least-once — may mint two live secrets

Rules:

  • The first request with a given key does the work.
  • A retry with the same key and same body returns the same result, including the original ids. That is how you tell a replay from fresh work: the change you get back carries the first request’s id, not a new one.
  • The same key with a different body is a 409 conflict. Use a fresh key for a different operation.
  • A key is scoped to one Brain and the key that sent it, and it is kept for as long as the Brain is. There is no expiry window to race.

Nicia deliberately does not derive a key from a hash of your request body. That would make two intentionally identical operations — two observations of the same measurement, two keys with the same name, a batch you meant to send twice — permanently indistinguishable.

Use something stable for the logical operation, not something random per attempt: sync:acme:2026-08-14, app-key:acme, an order id, or a UUID you generate before the first attempt and reuse across retries.

A genuine second operation deserves a genuine second key. If a nightly job runs twice on purpose, fold the run into the key.

Retry 429 and 5xx with exponential backoff, honouring Retry-After when it is present. Do not retry other 4xx — they describe a problem with the request that will answer the same way again. See Errors.

The rule for repeating a write is the same either way: an addressed write is always safe; a non-addressed write is safe when it carries a key.

POST /v1/runs follows the same Idempotency-Key contract, and it composes with Prefer: wait — retrying a waiting request with the same key re-attaches to the original run and returns its current state.