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.
Addressed writes are naturally idempotent
Section titled “Addressed writes are naturally idempotent”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.
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
changeyou 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.
Choosing a key
Section titled “Choosing a key”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.
What to retry
Section titled “What to retry”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.
On the agent platform
Section titled “On the agent platform”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.