Idempotency and retries

Write clients should be able to retry after network, rate-limit, and transient server failures without creating duplicate CRM records, tasks, activity entries, assets, or webhook state.

When to use a key

Send an idempotency_key on every create or write-style operation that might be retried by a client, job runner, webhook worker, or agent loop.

Use the same JSON body and the same key when retrying the same logical write:

Idempotent createbash
curl -X POST "$SLAB5_API_BASE_URL/contacts" \
  -H "Authorization: Bearer $SLAB5_WORKSPACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jane Doe",
    "email": "jane@acme.com",
    "company_name": "Acme Corp",
    "idempotency_key": "contact_jane_doe_acme_2026_05_03"
  }'

Reads do not need idempotency keys. Updates that are already naturally idempotent can still include a key when the caller wants a support-friendly retry trail.

Key design

Use keys that are stable for one logical operation and unique across unrelated writes:

  • Good: contact_jane_doe_acme_2026_05_03
  • Good: webhook_delivery_evt_123_attempt_1
  • Good: lead_intake_run_abc_create_task
  • Avoid: retry
  • Avoid: create_contact
  • Avoid: a request ID from a previous response

Do not put secrets, raw email bodies, full prompts, or sensitive personal details in idempotency keys. They may appear in logs and support tooling.

Retryable errors

Retry only errors marked retryable in the error reference. Current retry candidates are:

  • rate_limited: wait for retry_after_seconds or the reset time when present.
  • internal_error: retry with bounded exponential backoff and jitter.
  • Network timeouts where the client did not receive a response: retry the same body and key.

Do not retry validation, authentication, authorization, missing-scope, module-disabled, workspace, or not-found errors until the request or credential is fixed.

Conflict behavior

If a key is reused with a different body, Slab5 returns idempotency_conflict:

Conflict responsejson
{
  : var(--shiki-token-string)">"color: var(--shiki-token-parameter)">: var(--shiki-token-string)">"error": {
    : var(--shiki-token-string)">"color: var(--shiki-token-parameter)">: var(--shiki-token-string)">"code": : var(--shiki-token-string)">"idempotency_conflict",
    : var(--shiki-token-string)">"color: var(--shiki-token-parameter)">: var(--shiki-token-string)">"message": : var(--shiki-token-string)">"The idempotency key was reused with a different request body.",
    : var(--shiki-token-string)">"color: var(--shiki-token-parameter)">: var(--shiki-token-string)">"idempotency_key": : var(--shiki-token-string)">"contact_jane_doe_acme_2026_05_03",
    : var(--shiki-token-string)">"color: var(--shiki-token-parameter)">: var(--shiki-token-string)">"request_id": : var(--shiki-token-string)">"req_idempotency_123",
    : var(--shiki-token-string)">"color: var(--shiki-token-parameter)">: var(--shiki-token-string)">"docs_url": : var(--shiki-token-string)">"https://docs.slab5.com/api/errors#idempotency_conflict"
  }
}

Treat conflicts as client bugs or duplicate job-run bugs. Generate a new idempotency key only when the user or workflow is intentionally making a new write.

Client checklist

  • Generate the idempotency key before the first write attempt.
  • Persist the key with the job, queue item, or agent state.
  • Retry with the exact same body and key.
  • Log the returned request_id.
  • Stop retrying when the error is not retryable.
  • Use bounded retry limits so agent and webhook loops cannot run forever.

Was this page helpful?