Idempotency

Use an idempotency key to make Dairo creates safe to retry, so a repeated request returns the original result instead of a duplicate.

Networks drop, requests time out, and agents re-run steps, so the same POST sometimes fires twice. An idempotency key makes that safe: a retry with the same key returns the original result instead of creating a duplicate.

Send an idempotency key

Pass an Idempotency-Key header, with a unique value per logical operation, on the operations listed under Where it applies. A UUID is a good default.

curl -X POST https://api.dairo.app/v1/messages \
  -H "Authorization: Bearer $DAIRO_API_KEY" \
  -H "Idempotency-Key: 2f1c7e9a-8b40-4d2e-9c6a-1f0e5b3a7d21" \
  -H "Content-Type: application/json" \
  -d '{"inboxId":"inbox_123","to":"dev@example.com","subject":"Hi","text":"Hello"}'

Retrying that exact request with the same key returns the original message instead of sending a second one.

// The key is a per-call option on every create.
await dairo.messages.send(
  { inboxId: "inbox_123", to: "dev@example.com", subject: "Hi", text: "Hello" },
  { idempotencyKey: "2f1c7e9a-8b40-4d2e-9c6a-1f0e5b3a7d21" },
);

The key can be up to 128 characters; a longer value is rejected with 400. A blank or whitespace-only header is treated as absent.

Header and body agree

Some creates also accept an idempotencyKey in the JSON body. Dairo reconciles the header and the body field before running the request:

You sendResult
Header onlyThe value is used.
Body field onlyThe value is used.
Both, equalAccepted.
Both, different non-empty values400 with code idempotency_key_mismatch.

Set the key either way. A disagreement is always surfaced as an error, never resolved silently.

Where it applies

The key is honored on the operations where a duplicate would be costly or irreversible:

EndpointWhat a retry de-duplicates
POST /v1/messagessending (or scheduling) the same message twice
POST /v1/audiences/{id}/sendbroadcasting to an audience twice
POST /v1/letters, POST /v1/letters/batchesprinting and posting the same physical letters twice
POST /v1/letters/{id}/cancelcanceling twice
POST /v1/phone/callsringing the same person twice
POST /v1/erasure-jobsenqueuing the same erasure job twice
POST /v1/inboxes/{id}/verification-waitsregistering the same wait twice

Other creates do not use the key. Some of them are safe to retry anyway because the resource is identified by what you send: creating an inbox or a domain that already exists returns it, and adding audience members upserts them by address. Creating a webhook for a URL you already registered, a bucket with a name in use, or a template with a slug already in use, returns 409. Creating an API key, an audience, an agent passport, or a share link mints a new one on every call, so create those once and store the result.

GET requests are always idempotent and need no key. DELETE and PATCH are naturally idempotent (deleting or setting the same state twice has the same effect), so they do not take an Idempotency-Key.

Reuse the key, keep the payload

A key identifies one specific operation, not a slot you can overwrite. On a message send, reusing a key with a different subject or to returns the original send unchanged, plus a warning (reason: "idempotency_key_reused_with_different_params"); your new content is not sent. To send genuinely different content, use a fresh key.

Two habits keep this predictable:

  • Generate the key before the first attempt and reuse it across every retry of the same operation. A fresh key per attempt defeats the purpose, since each new key is a new operation.
  • Use a stable, unique value. A UUID is the simplest safe choice. If you key off your own data, make the value unique per intended operation, for example order-4815-welcome-email.

The SDKs retry transient failures for you; passing an idempotency key makes those retries duplicate-safe end to end.

On this page