Skip to content

Idempotency

Safely retry mutation requests without creating duplicate resources.

Overview

The Contio Partner API supports opt-in idempotency on all JSON-body mutation endpoints (POST, PUT, PATCH, DELETE) on the Admin and User surfaces. Multipart/file-upload endpoints are excluded. Send an Idempotency-Key header with your request and the API will deduplicate retries automatically — the first request executes normally and its successful response is stored; subsequent retries with the same key replay the original response instead of re-executing.

Requests without the header behave exactly as before (non-idempotent).

Quick Start

Add an Idempotency-Key header to any mutation request:

curl -X POST https://api.contio.ai/v1/partner/user/meetings \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: my-unique-key-123" \
  -d '{"title": "Weekly Standup", "workspace_id": "..."}'

If the request succeeds (2xx) and you retry with the same key, the API returns the original response with an additional header:

HTTP/1.1 201 Created
Idempotent-Replayed: true
Content-Type: application/json

{"id": "...", "title": "Weekly Standup", ...}

How It Works

flowchart TD
    A[Partner sends mutation request] --> B{Idempotency-Key present?}
    B -->|No| L[Legacy path — execute normally, no dedup]
    B -->|Yes| C[Store PENDING record keyed on the header]
    C -->|First request| D[Execute handler]
    D --> E{2xx response?}
    E -->|Yes| F[Store response → COMPLETED]
    E -->|No| G[Release key — freely retryable]
    C -->|Duplicate key| H{Existing record state?}
    H -->|COMPLETED, same payload| I[Replay stored response]
    H -->|COMPLETED, different payload| J["422 idempotency_key_reuse"]
    H -->|PENDING — in flight| K["409 + Retry-After"]

Request Headers

Idempotency-Key

Property Value
Direction Request
Required No (opt-in)
Max length 255 characters
Allowed characters Printable ASCII (no control characters, no whitespace-only)

Use a value that is unique per logical operation — a UUID, a database row ID, or any string your system can reproduce on retry.

Key scoping

Keys are scoped to your partner app, the authenticated user (on the User surface), the workspace, the HTTP method, and the route. The same key string can safely be used across different endpoints or users without collision.

Response Headers

Idempotent-Replayed

Returned only on replay responses (when a completed record already exists for the key). The value is always true. Use this header to distinguish a fresh execution from a deduplicated replay in your logging or metrics.

Retry-After

Returned with a 409 Conflict response when another request with the same key is still being processed. The value is the number of seconds to wait before retrying.

Error Responses

All idempotency errors use the standard error envelope.

422 — Key Reuse

Returned when the same Idempotency-Key is sent with a different request payload (body or query parameters) than the original. This protects against accidental key reuse across unrelated operations.

{
  "error": "The idempotency key has already been used with a different request payload.",
  "code": "idempotency_key_reuse",
  "request_id": "req_abc123"
}

Resolution: Generate a new unique key for the new operation.

409 — In-Flight Conflict

Returned when a request with the same key is currently being processed. This is a transient condition.

{
  "error": "A request with this idempotency key is currently in flight.",
  "code": "idempotency_conflict",
  "request_id": "req_abc123"
}

Resolution: Wait for the duration indicated by the Retry-After header, then retry with the same key.

400 — Invalid Key

Returned when the key fails validation (exceeds 255 characters, contains control characters, or is whitespace-only).

{
  "error": "The Idempotency-Key header value is invalid.",
  "code": "idempotency_key_invalid",
  "request_id": "req_abc123"
}

Resolution: Use a shorter key with printable ASCII characters that is not whitespace-only.

Key Retention

Completed idempotency records are retained for 24 hours from the time the original request completed. After this window, the key expires and can be reused. Pending (in-flight) records expire after 5 minutes if the original request never completes.

Record State Retention
Completed (2xx stored) 24 hours
Pending (in flight) 5 minutes

Best Practices

  1. Always use idempotency keys for critical mutations — meeting creation, credential rotation, webhook delivery retries, and any operation where duplicates would be harmful.

  2. Generate keys client-side — use UUIDs, database sequence IDs, or any deterministic value your retry logic can reproduce.

  3. Do not reuse keys across different operations — the API enforces payload fingerprint matching and will return 422 if the body differs.

  4. Handle 409 with backoff — respect the Retry-After header to avoid overwhelming the API while a concurrent request completes.

  5. Non-2xx responses release the key — if your request fails with a 4xx or 5xx (other than idempotency errors), the key is released and you can retry with the same key and payload.

Supported Endpoints

Idempotency is supported on all JSON-body mutation endpoints (POST, PUT, PATCH, DELETE) on both the Partner Admin API (/v1/partner/admin/*) and the Partner User API (/v1/partner/user/*). Multipart/file-upload endpoints are excluded. GET and HEAD requests ignore the header since they are naturally idempotent.

The Idempotency-Key header is documented on each supported operation in the OpenAPI specification.