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
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:

text
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.

curl
# First attempt — times out before you see the response
curl -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 result
curl -sS -X POST "https://api.owlmar.com/v1/maintenance" \
-H "Idempotency-Key: 8f14e45f-2b3a-4c9e-9a1d-6e0f5c8b7a21" \
-d '{ "vesselId": "...", "description": "500-hour service" }'
Note

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:

json
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.

Warning

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).