Idempotency
A dropped connection should not become a second email or workflow run. On supported writes, send an Idempotency-Key so maxclicks can identify a retry of the same operation. Ordinary cached writes replay a recorded response; template sends and record deletion have their own durable operation rules below.
Keep the key, payload, and returned result together in your integration. If the outcome is uncertain, reuse the original key while you investigate; a new key represents a new operation.
The header
Choose a unique key for each logical operation, such as a UUID or a source event ID. Keys can be up to 256 characters. Send the same value and body for every retry of that operation.
curl -X POST https://api.maxclicks.ai/v1/schemas/customers/records/upsert \
-H "Authorization: Bearer $MAXCLICKS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: customer-sync-1042-revision-7" \
-d '{ "userId": "1042", "email": "[email protected]" }'
Replace customers with your contact schema's slug. Include a source revision in a sync key when the same customer's data can change later.
Lifecycle
| Result | What your integration should do |
|---|---|
| First request | Save the operation's key and body. The server claims the operation before executing it. |
| Completed request | Save the result and any Operation-Id response header. A repeat can return the recorded status and body with Idempotent-Replay: true. |
409 idempotency_conflict | The original is still processing. Back off and retry with the same key and body. |
409 idempotency_outcome_unknown | The original outcome is unresolved. Check the affected record, email, broadcast, or workflow before taking another action. |
422 idempotency_key_reused | The key was used with a different body. Recover the original payload; use a new key only for a separate operation. |
429 idempotency_admission_busy | Wait for Retry-After, then retry the same key and body. |
503 idempotency_outcome_unknown | The server could not confirm persistence of the outcome. Retain any operationId and reconcile; do not force another execution with a new key. |
Recorded errors and retention
Responses produced after an operation is claimed can be recorded, including 4xx and 5xx responses. Repeating the request can replay that error; it does not guarantee another execution. Authentication, request parsing, and other checks that run before the claim can fail without creating an idempotency record.
Completed ordinary cached operations have a nominal 24-hour retention period. After cache expiry, an ordinary opaque key can execute again. Unresolved operations are retained for reconciliation; elapsed time alone does not make a retry safe. Timed record writes, template sends and deletion attempts have different rules.
Record writes with AI auto-fill
When record create/upsert needs missing AI auto-fill, use a timed key in the form rpi1.<13-digit Unix milliseconds>.<UUIDv4>. Create it once for the operation and persist it with the original input. The rpi1. prefix is reserved; an invalid shape is refused.
Its retry window ends 24 hours after the issue time rounded up to the next minute. A clock more than five minutes ahead is refused. After expiry, the key is permanently refused with 410 record_operation_expired, including after response cleanup. Do not make a fresh key automatically to bypass expiry: inspect the record and original operation first.
A retry of an erased record response returns 410 instead of private data. A recorded record-write error can retain a stable code with a constant message, so save detailed validation information from the first response. See record writes and errors.
Template sends
templates.send requires a non-blank key. It retains the message and recipient from the original operation instead of using generic cache expiry or replay headers. A temporary refusal returns 429 template_send_retryable with Retry-After; wait and retry the same key, template and input. An uncertain outcome returns 409 template_send_outcome_unknown; reconcile it before taking another action. A confirmed terminal failure remains failed on retry.
See the template-send contract for conflicts and outcome checks. A new key requests another send.
Record deletion attempts
records.delete requires an explicit attempt key of 1–128 characters. Start with a letter or digit, then use letters, digits, underscores, periods, colons or hyphens. Keep that key to recover a lost response and save the returned operationId.
The key identifies the deletion attempt; the endpoint returns current operation status rather than a cached response. Poll deletion status. After deletion completes, the DELETE route can return 404 because the record is gone, so use the saved operation ID to inspect completion.
The original requester with current write permission can cancel before removal begins. Cancellation waits for intake retirement; 202 is not a completed cancellation. Poll until cancelled. A later intentional deletion after cancellation needs a new attempt key; repeating the cancelled attempt does not request another deletion.
Do not assume every 5xx is safe to resend as a new operation. A write may
have taken effect before the response was lost. When the API reports an
unresolved outcome, inspect the resource or its run history before deciding
what to do next.
What a key is scoped to
The server scopes a key to the calling API key, HTTP method, and resolved path. It also compares a canonical hash of the request body. The same key sent to two different template IDs represents two operations; the same key and path with a changed body returns 422 idempotency_key_reused.
Reordering JSON object keys does not change the body identity. Changing values does. Reuse the same calling API key for retries, including while rotating credentials.
Supported endpoints
| Operation | Method and path |
|---|---|
records.create | POST /v1/schemas/{schema}/records |
records.upsert | POST /v1/schemas/{schema}/records/upsert |
suppressions.batchCreate | POST /v1/contacts/suppressions/batch |
suppressions.batchDelete | POST /v1/contacts/suppressions/batch-remove |
suppressions.delete | DELETE /v1/contacts/suppressions/{id} |
segments.create | POST /v1/segments |
segments.delete | DELETE /v1/segments/{id} |
webhooks.create | POST /v1/webhooks |
webhooks.update | PATCH /v1/webhooks/{id} |
webhooks.delete | DELETE /v1/webhooks/{id} |
webhooks.rotateSecret | POST /v1/webhooks/{id}/rotate-secret |
workflows.trigger | POST /v1/workflows/{id} |
workflows.pause | POST /v1/workflows/{id}/pause |
workflows.unpause | POST /v1/workflows/{id}/unpause |
broadcasts.send | POST /v1/broadcasts/{id}/send |
broadcasts.update | PATCH /v1/broadcasts/{id} |
templates.send | POST /v1/templates/{templateId}/send |
records.delete | DELETE /v1/schemas/{schema}/records/{id} |
For workflow triggers, the path uses the incoming-webhook step's reference ID. Copy it from the workflow; it is not the workflow's own ID.
Webhook creation and secret rotation replay their original secret-bearing response when retried with the same key. Store that result securely. Normal webhook GET/list responses omit the signing secret. A new key on rotation requests another rotation.
The SDK guides show how to pass an idempotency key in each language. For direct HTTP requests, set the Idempotency-Key header; adding a field to the JSON body does not set an HTTP header.
Other retry mechanisms
The header does not add protection to an endpoint outside this table. Record PATCH and single suppression creation do not use this mechanism. Template sends and record deletion use the distinct required identities explained above.
- Business event creation uses your
eventId, scoped to the space and event schema. Preserve the same payload and explicitoccurredAt. Changed input returnsevent_id_conflict; a duplicate returns its original acceptance receipt. Poll readiness separately. - Event identity changes use the required UUID
operationIdplusexpectedAliasRevision. Preserve the input when retrying and save the returned revision for that alias. - Cancellation of a record deletion uses the existing
operationId, with no additional idempotency key. Inspect its state untilcancelled; cancellation cannot restore removed records.