Skip to navigation

Idempotency and replay

Retry a mutation safely after a lost answer.

Write operations take an Idempotency-Key header of 1 to 255 characters. Draft and tool validation, campaign recipient checks, POST /calls/search and POST /usage/query do not change a resource and take no key.

The rules

  • One key per intended action. Generate a new key, for example a UUID, for each action you mean to take. Persist it before you send the request.

  • Same key, same request: the original answer. Sending the same key with the same request again returns the original status code and body, with the response header Idempotent-Replayed: true. Nothing happens a second time: a replayed createCall does not place a second call.

  • Same key, different request: refused. A changed method, path, body or original If-Match under a used key answers 409 idempotency_key_reused. Fix the caller; do not retry a changed intent with the old key.

  • Still running: wait. If the first request with the key is still in progress, a duplicate answers 409 idempotency_in_progress with Retry-After at once; it does not wait for the first request. Wait the stated seconds, then resend the identical request with the same key: it then replays the first answer. This holds for every keyed mutation.

  • 24 hours. Keys are scoped to your workspace and to the operation, and are kept for 24 hours. After that, read the resource to find out what happened before you act again. This is not permanent deduplication.

  • Authorization is checked again. A replay still requires a key that is valid now.

What a replay returns

A replay returns the stored receipt of the first attempt: it describes that attempt, not the resource as it is now. The server looks the key up before it checks the current revision, so a replay of an edit returns its original answer even if the resource has changed since.

OperationsWhat is storedWhat a replay answers
Agent, draft, variable, publish, rollback, tool and MCP server writesThe full public answer and its ETagThe original status, ETag and body, even after the resource was changed or detached, or its agent removed
createCallThe accepted call: its ID, the executed version, the state, the source number ID and the poll hint. The dialled number, the variables and the metadata are never stored in it.The original body, naming the same call. The number is never dialled twice.
endCallThe full answerThe original body
Phone-number assignment and provisioningThe full answer (the number, or the number with its setup state)The original body with its original ETag
Hosted share-link issue and rotationSafe metadata without the bearer tokenMetadata with token: null; the original token is revealed once
createSecret, rotateSecret, revokeSecretVersion, deleteSecretThe metadata answer (for deleteSecret, the 204)The original status and body; the secret is not read again
Reads, draft/tool validation, campaign recipient checks, call search and usage queryNothing: they take no keyNot applicable

No receipt holds a credential value. The rule behind the table: a replay returns the original body, except that a body carrying personal data that can be erased (phone numbers, variables, transcripts) is kept as identifiers only and read again, because erasure must not leave that data in a 24-hour receipt. The answers above are designed not to carry such data, which is why they can be stored whole.

Refusals

A refused request can also be stored, and then replays as the same refusal for the key’s 24 hours. Two cases differ:

  • createCall refused with 503 at_capacity or 429 quota_exceeded: wait Retry-After, then send the identical request with the same key. It is admitted afresh.
  • The secret operations store nothing when they refuse. After you fix the cause, the same request with the same key runs.

A safe retry

KEY=$(uuidgen)
curl -sS -X POST "$RASEN_API_BASE/agents" \
-H "Authorization: Bearer $RASEN_API_KEY" -H "Idempotency-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Support line", "resource_key": "support-line"}'
# The connection dropped before the answer arrived? Send the identical request again with the same $KEY.

For internal_error, provider_unavailable, unavailable, at_capacity and call_orchestrator_unavailable, a retry with the same key is safe: if the first attempt took effect, the stored outcome is replayed; if it had no effect, the retry performs the operation once. Never retry with a new key.

Keys are not resource keys

An Idempotency-Key protects one request. A resource_key names a piece of configuration across script runs, and is not an idempotency key. See Resource keys.

Key rotation on the server

If the platform rotates its master key material, idempotency keys used in the previous 24 hours answer idempotency_key_expired. Read the resource to reconcile, then send a new request with a new key.