Idempotency
Network calls fail. When a write request times out, you don't actually know whether it
succeeded on the server before the connection dropped — retrying blind risks creating the same
maintenance event, expense, or booking twice. The Idempotency-Key header solves this.
When to use it
Add an Idempotency-Key header to any POST, PATCH, or DELETE you might need to retry —
in practice, any write your integration issues from code that has retry logic at all (which it
should).
curl -sS -X POST "https://api.owlmar.com/v1/maintenance" \-H "Authorization: Bearer $OWLMAR_API_KEY" \-H "Content-Type: application/json" \-H "Idempotency-Key: 8f14e45f-2b3a-4c9e-9a1d-6e0f5c8b7a21" \-d '{"vesselId": "3f1c9e2a-...","equipmentId": "8b4d0f11-...","eventType": "routine","scheduledDate": "2026-01-30T09:00:00.000Z","description": "500-hour service — port main engine"}'
It's opt-in — a missing header is always a no-op, including for writes. GET requests
ignore the header entirely even if you send it.
24h window
Replaying the exact same (method, path, request body) under the same key within 24 hours
returns the original response byte-identical, with an extra header confirming it was a replay:
201 Created
Idempotent-Replayed: true
{ "data": { "id": "9c2e1a04-...", "description": "500-hour service — port main engine" } }This means the safe retry pattern is: generate the key once per logical attempt, then reuse that same key across every retry of that same attempt — not a fresh key per HTTP call.
# First attempt — times out before you see the responsecurl -sS -X POST "https://api.owlmar.com/v1/maintenance" \-H "Idempotency-Key: 8f14e45f-2b3a-4c9e-9a1d-6e0f5c8b7a21" \-d '{ "vesselId": "...", "description": "500-hour service" }'# Retry with the SAME key and SAME body — safe, returns the original resultcurl -sS -X POST "https://api.owlmar.com/v1/maintenance" \-H "Idempotency-Key: 8f14e45f-2b3a-4c9e-9a1d-6e0f5c8b7a21" \-d '{ "vesselId": "...", "description": "500-hour service" }'
A uuid is a good default key generator, but any sufficiently unique string works — some
integrations derive it from a source-system record ID (e.g. erp-invoice-48213) so a retry
from an entirely separate process still lands on the same key.
Request-body drift → 409
Reusing an Idempotency-Key with a different request body is treated as a client bug, not
served silently:
409 { "error": "Idempotency-Key reused with a different request body", "code": "CONFLICT" }
This is deliberate — silently serving the stale first response would hide a real bug in your
retry logic (you changed the payload and didn't realize the key was still the old one); silently
re-executing with the new body would defeat the entire point of idempotency. Neither is safer
than a loud 409.
If you see 409 CONFLICT on a write you expected to be idempotent, check your key-generation
logic first — it usually means a key is being reused across what your code thinks are two
different logical operations.
The window is 24 hours from the first successful write; after that, the same key on the same body executes fresh (a new record is created, not deduplicated against the aged-out one).
