Idempotency
This article and the rest of the API documentation in this section are written for a technical audience — integrators and developers connecting external systems to Tickiti. Familiarity with HTTP, REST, JSON and bearer-token authentication is assumed.
Network calls fail. They time out, drop connections, return 502 from a flaky proxy. When that happens to a write request, the calling system has to decide whether to retry — and risk creating duplicates — or back off and risk losing the work.
Tickiti solves this with idempotency keys. Every API write requires an Idempotency-Key header. The key is a unique identifier for the logical operation; Tickiti remembers it for a long-enough window that a retry with the same key replays the original outcome rather than redoing the work.
The contract
- Same key + same payload → Tickiti returns the previously stored response. Safe to retry as many times as you like.
- Same key + different payload → Tickiti returns
409 idempotency_key_conflict. Always a bug on the caller side — the key has been reused with the wrong content. - Different key → Tickiti executes the request normally.
The header is mandatory on the legacy write endpoints: POST /api/create_ticket, POST /api/ticket_respond, POST /api/send_email and POST /api/render_template. The unified endpoints (POST /api/tickets/create and POST /api/tickets/{number}/respond) do not require Idempotency-Key — they trade off the safety net for a leaner request shape, so if you need retry safety you should be using the legacy endpoints.
Generating keys
Two options:
- Generate locally — any UUIDv4 (or other unique-enough string up to 255 chars) works. The simplest pattern: pin the key to the logical operation in your system.
order:SP-2412-0489:warrantyas a deterministic key means a retry of the same warranty filing reuses the same key automatically. - Pre-create via Tickiti —
POST /api/idempotency_keyreturns a fresh UUID-based key bound to a specific action (send_emailorcreate_ticket). Use this if you want Tickiti to mint the key for you.
Pre-creation example:
curl -X POST https://support.sole-provider.example/api/idempotency_key \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "action": "create_ticket" }'
# response:
{ "ok": true, "data": { "idempotency_key": "7b4f3c2e-9d6a-4f8b-9e7c-2c3a4b5e6f7a", "action": "create_ticket" } }Retry strategy
- Generate (or fetch) the key once at the start of the logical operation.
- Send the request with that key.
- On a 5xx, network timeout, or no response, retry with the same key. Repeat with exponential backoff up to your retry budget.
- On a 2xx, you are done.
- On a 4xx other than
409, do not retry — the request will fail again. - On a
409 idempotency_key_conflict, the key was reused with a different payload. This is a bug; do not retry; investigate which earlier request collided.
Retention window
Idempotency records are pruned by housekeeping on a schedule:
- Completed records — kept for
idempotency.retention_days(default 7 days) past completion, then removed. A retry of a key older than that is treated as a brand-new request, so plan retries to fit comfortably inside the window. - In-flight records — if a key was created but never completed (the server crashed mid-request, say), the stale record is cleared after
idempotency.stale_inflight_hours(default 6 hours) so it cannot block a fresh attempt forever.
Both windows are configured in config/idempotency.php on the Tickiti install; the defaults are conservative and rarely need changing.
Common mistakes
- Generating a fresh UUID inside the retry loop. The whole point is the key stays the same across retries. Generate once.
- Reusing a key across logically different operations. Same key + different payload = 409. Each logical operation needs its own key.
- Treating 409 as "try again with a new key". It is not — it means you have a bug. Surface it; do not paper over it.