DocsGuides

Idempotency

Make writes safe to retry with the Idempotency-Key header.

Network calls can fail after the server has already processed them. Idempotency lets you retry a write safely: if the original request succeeded, the retry returns the same result instead of performing the action twice.

When it is required

The Idempotency-Key header is required on these writes:

  • POST /reservations
  • PATCH /reservations/{id}
  • POST /orders

A write that needs a key but is sent without one is rejected with 422 validation_error. Reads (GET) are naturally idempotent and ignore the header.

How it works

Send a unique key per logical operation, typically a UUID v4:

curl -X POST https://coastly.surf/api/v1/reservations \
  -H "Authorization: Bearer $COASTLY_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f9a1c7e-2b6d-4a11-9c2e-8d7f4b1a0e55" \
  -d '{ "property_id": "prop_1a2b3c", "check_in": "2026-07-12", "check_out": "2026-07-15", "guests": 2, "rooms": [{ "room_type_id": "rt_ocean_king", "units": 1, "guests": 2 }] }'
  • First request: the action is performed and the response is stored against the key.
  • Retry with the same key and the same body: the stored response is replayed byte for byte, including the original status code. The action does not run again.
  • Same key with a different body: the request is rejected with 409 idempotency_conflict. A key is bound to the exact request it first saw.

Choosing keys

  • Generate a fresh key for each distinct operation you want to perform once. For example, one key per booking attempt.
  • Reuse the same key when retrying that same operation after a timeout or network error.
  • Do not reuse a key across genuinely different operations; that either replays the wrong stored response or triggers a conflict.

Recommended retry pattern

import { randomUUID } from "node:crypto";

async function createReservation(body) {
  const key = randomUUID(); // one key for this booking attempt
  for (let attempt = 0; attempt < 4; attempt++) {
    const res = await fetch("https://coastly.surf/api/v1/reservations", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.COASTLY_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": key, // same key across retries
      },
      body: JSON.stringify(body),
    });
    if (res.status === 429 || res.status >= 500) {
      await new Promise((r) => setTimeout(r, 2 ** attempt * 250));
      continue; // safe: same key replays, never double-books
    }
    return res.json();
  }
  throw new Error("reservation failed after retries");
}

Because retries carry the same key, a booking is created at most once even if the client cannot tell whether the first attempt landed.