Idempotency
Retry a mutating request without doing the work twice
Network calls fail halfway. A client that retries a "send chat", "fire trigger", or "create scheduled job" request has no way to know whether the first attempt took effect. Idempotency keys close that gap: a retry carrying the same key replays the original response instead of running the request again.
The header
Clients send an X-Muxi-Idempotency-Key header on mutating requests. All MUXI
SDKs generate one automatically (set once per logical request, reused across the
SDK's own retries), so idempotency is on by default when you use an SDK. When
calling the API directly, supply your own unique value per logical operation:
curl -X POST http://localhost:7890/api/my-assistant/v1/chat \
-H "X-Muxi-Client-Key: $CLIENT_KEY" \
-H "X-Muxi-User-Id: user-123" \
-H "X-Muxi-Idempotency-Key: 7b2f9c1e-3a44-4c8d-9f21-1e6b0d8a55aa" \
-H "Content-Type: application/json" \
-d '{"message": "Charge my account and summarize the invoice"}'
Where it applies
The runtime honours idempotency keys on these mutating Formation API endpoints:
| Endpoint | Method |
|---|---|
Chat (/v1/chat)
| POST |
Execute trigger (/v1/triggers/{trigger_name})
| POST |
Create scheduled job (/v1/scheduler/jobs)
| POST |
Requests without the header run unchanged - the feature is entirely opt-in per request.
Semantics
- Only successful (2xx) JSON responses are cached. Errors are always retryable, so a failed attempt never poisons the key.
- Streaming (SSE) responses pass through untouched. A token stream can't be
replayed from a response cache. A repeated streaming request executes again,
so use
stream: falsewhen you need idempotent chat retries. - Concurrent requests with the same key are single-flighted. The second caller waits for the first to finish, then receives the same cached response rather than racing it.
- Keys are scoped per method, path, and user. Different callers, endpoints, or path parameters (e.g. two different trigger names) can reuse the same key value safely.
- Storage is in-process with a 24-hour TTL. Like async request state, cached responses do not survive a runtime restart.
The echoed key
When a request carries an idempotency key, the runtime echoes it back on the
response envelope under request.idempotency_key:
{
"object": "chat_response",
"request": {
"id": "req_abc123",
"idempotency_key": "7b2f9c1e-3a44-4c8d-9f21-1e6b0d8a55aa"
},
"success": true,
"data": { }
}
All SDKs surface this echoed value on the unwrapped response (Go exposes it via
RequestInfo.IdempotencyKey; the others carry idempotency_key on the
unwrapped result), so a client can confirm which key a response belongs to.
Learn More
- API Reference - request headers and envelope
- Resilience - retries, timeouts, and graceful degradation
- Request Lifecycle - how a request flows through the runtime