View as Markdown

Idempotency

Any POST, PATCH, PUT or DELETE on /v1 can carry an Idempotency-Key header:

POST /v1/projects
Idempotency-Key: create-demo-project-2026-10-05

The key is 1 to 255 characters: letters, digits, underscore and hyphen. A malformed key is refused with 400 invalid_idempotency_key.

What happens

"The same request" means the same method, path and body. The query string is not part of it.

Whose keys they are

Keys are kept apart per caller. For an app, that is per app, per key and per company the call works in. Two apps, two keys of one app, or one key in two companies never share a kept response, even with the same Idempotency-Key.

What is never kept

There is no lock while a call is running. If two calls with the same new key arrive at the same moment, both run. Send a retry only after the first call has answered or timed out.

SDK support

@exayard/sdk sends an Idempotency-Key on every POST, PATCH, PUT and DELETE by itself, so its own retries never do the work twice. Pin your own key when you retry from a persistent queue, so a retry after a restart uses the same key:

await exa.projects.create({ name: 'Demo' }, { idempotencyKey: `queue-job-${jobId}` })