Documentation
Partner API

Idempotency

Idempotency-Key makes a create safe to repeat: the first answer is stored 24 hours and replayed with Idempotent-Replayed: true; a reused key answers 409.

Updated 2026-10-24MarkdownOpenAPI

A retried POST must never create a twin. The Idempotency-Key header makes a create safe to repeat: the first answer is stored and replayed to every retry with the same key, for 24 hours.

Where it applies

  • POST /v1/orders — required; 422 IDEMPOTENCY_KEY_REQUIRED without it.
  • POST /v1/files, POST /v1/quotes, POST /v1/webhook-endpoints — optional and recommended.
  • Reads, PATCH and DELETE need none: they are idempotent by nature.

The key

Any string of 1 to 255 characters of letters, digits and . _ : ~ -, unique per attempt — a UUID v4 is the easy choice. Keep it free of personal data. Store it with the thing you are creating (your shop order, say) before you send the request, so a crash between the request and the response is recovered by sending the same request again with the same key.

How it behaves

SituationAnswer
First request with a keyThe real answer — 202 for an order.
Same key, same body, within 24 hoursThe stored answer, with the header Idempotent-Replayed: true; 200 for an order.
Same key, different body409 IDEMPOTENCY_KEY_REUSED. Use a new key for a new request.
Same key while the first request is still running409 IDEMPOTENCY_IN_PROGRESS with Retry-After: 2. Send the same request again after that.
Same key after 24 hoursTreated as a new request.

Only successful answers (2xx) are stored. When the first attempt failed with a 4xx — a validation error, say — nothing is kept, and a corrected request with the same key is processed as new. Keys are per partner and per environment: the same string in test and in live are two keys.

A safe retry loop

Node
import crypto from "node:crypto";

// One key per shop order, persisted before the first attempt.
async function createOrder(shopOrder) {
  const key = shopOrder.layercrewIdempotencyKey ?? (await persistKey(shopOrder, crypto.randomUUID()));
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch("https://engine.layercrew.com/v1/orders", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.LAYERCREW_API_KEY}`,
        "Idempotency-Key": key,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(toLayerCrewOrder(shopOrder)), // the same body every time
    });
    if (res.ok) return (await res.json()).data;             // 202, or 200 with Idempotent-Replayed: true
    if (res.status === 409) {
      const { error } = await res.json();
      if (error.code === "IDEMPOTENCY_IN_PROGRESS") { await sleep(2000); continue; }
      if (error.code === "REFERENCE_TAKEN") return getOrder(error.details.order_id);
      throw new Error(`${error.code}: ${error.message}`);
    }
    if (res.status === 429 || res.status >= 500) { await sleep(1000 * 2 ** attempt); continue; }
    const { error } = await res.json();
    throw new Error(`${error.code}: ${error.message}`);          // 4xx: fix the body, keep the key
  }
  throw new Error("order creation did not settle; look it up by reference");
}

We value your privacy

We use cookies for analytics and advertising to improve your experience and measure our marketing. See our Privacy Policy.