Idempotency
How retry-safe mutations work in the Hatched API — the Idempotency-Key contract, what the platform caches, and how the SDK uses it automatically.
Hatched accepts an Idempotency-Key header on every mutating endpoint
(POST, PUT, PATCH, DELETE). When you supply one, the platform
guarantees that retries of the same request return the same response
— body, status, and side effects — for 24 hours.
This is the same shape Stripe, Slack, and AWS use. Apply it whenever a network blip, timeout, or client crash could cause you to repeat a request that should run exactly once.
How it works
- You send
Idempotency-Key: <unique-string>alongside the request. - The interceptor hashes the request method, path, and body to produce a fingerprint.
- Cache miss → handler runs normally. The response (body + status)
is cached under
idem:{customer_id}:{key}for 24 hours. - Cache hit, matching fingerprint → the cached response replays.
Idempotency-Replayed: trueis set on the response so you can tell the difference in a log line. - Cache hit, different fingerprint →
409 idempotency_key_conflict. You reused the key for a different request. Use a fresh key.
Failed responses (4xx / 5xx) are not cached — retries should
produce a fresh attempt. Cache only sticks on 2xx success.
Picking a key
A good key is:
- Unique per logical action. Use the business id (
order_<id>,lesson_<lessonId>_user_<userId>), not a clock value. - Stable across retries. The whole point: a retry must reuse the original key.
- Opaque to the user. Never expose it in a URL.
UUID v4 works fine when you don't have a natural business id. The SDK auto-generates one for you (next section).
SDK behaviour
@hatched/sdk-js injects Idempotency-Key automatically on mutating
resource methods. Most callers never have to think about it:
// Auto-generated, retried safely on transient network errors.
await hatched.eggs.create({ userId: 'user_42', ensure: true });To pass an explicit key from your own business logic — recommended for operations you may retry from a different process — set it via the options object:
await hatched.events.send(
{
eventId: 'lesson_42_user_99',
userId: 'user_99',
type: 'lesson_completed',
properties: { … },
},
{
headers: { 'Idempotency-Key': `lesson:42:user:99` },
},
);What this is not
- Not event-level deduplication. Use
eventIdon send events for that — it lives in the rule-engine, not the HTTP layer. - Not webhook idempotency. That contract is on the consumer side — see Webhook delivery.
- Not a queue. Replays return the cached response immediately; they don't re-enqueue work.
When to skip it
Read-only requests (GET, HEAD) ignore the header — they're naturally
idempotent. One-shot operations that intentionally produce a side effect
each time (charging a credit grant, issuing a unique token) should reuse
a different key per attempt rather than letting the platform replay.
If you need a route that explicitly opts out of idempotency caching, flag it on your side — Hatched will keep the header behaviour but you can set the key to a unique UUID per attempt to force a fresh run.
Auth model
Secret keys, publishable keys, widget session tokens, and embed tokens — which one to use, when, and why.
Pagination
Hatched ships two pagination envelopes — cursor (canonical for new endpoints) and offset (legacy). This page documents both shapes, the SDK helpers that walk them, and how to add cursor pagination to a server-side route.