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¶
-
Always use idempotency keys for critical mutations — meeting creation, credential rotation, webhook delivery retries, and any operation where duplicates would be harmful.
-
Generate keys client-side — use UUIDs, database sequence IDs, or any deterministic value your retry logic can reproduce.
-
Do not reuse keys across different operations — the API enforces payload fingerprint matching and will return
422if the body differs. -
Handle
409with backoff — respect theRetry-Afterheader to avoid overwhelming the API while a concurrent request completes. -
Non-2xx responses release the key — if your request fails with a
4xxor5xx(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.