Rate limits and idempotency


What the ceilings are, how a 429 tells you when to come back, and why a retry without a key builds the order twice.

Idempotency

Pass an idempotencyKey on every POST /v1/orders. It is optional, and leaving it out is the single most expensive mistake you can make against this API.

curl https://api.layout.link/v1/orders \
  -H "Authorization: Bearer $LAYOUT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "usr_4b8e",
    "placeId": "plc_sweetgreen_market",
    "query": "harvest bowl, no onion, and a lemonade",
    "idempotencyKey": "your-own-unique-id"
  }'

With a key, a retry returns the original order. Without one, Layout mints a fresh key per call, so a timeout you retry becomes two builds: two browser sessions, two runs against the restaurant's site, two draws on your daily budget. It cannot double-charge anyone, because building is not placing and placement happens on the hosted link. It can absolutely double your bill and confuse your user with two carts.

Use something stable from your own system: a cart id, a request id, whatever you would use to recognise the same intent twice. A new attempt the person genuinely asked for gets a new key.

The key is scoped to your application and the user, so it cannot collide with another partner's.

Per-minute limits

EndpointPer credential
POST /v1/orders60 / minute
GET /v1/orders/:id600 / minute
GET /v1/events120 / minute

Daily ceilings

These are the ones that actually stop you, and they reset at UTC midnight. Approval moves you up a tier.

CeilingSandboxProduction
Users provisioned, per day200500
Order builds, per day505,000
Order builds, per user per day2525

Sandbox ceilings are small on purpose. They are sized for developing against, not for load testing: a soak test belongs in your own stubs, not against real restaurant websites.

Handling a 429

Every rate-limited response carries rate_limited and, where Layout can compute one, a Retry-After header in seconds. Honour it. A daily ceiling returns the same code with no useful retry time, because the answer is UTC midnight.

HTTP/1.1 429 Too Many Requests
Retry-After: 34

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Retry later."
  }
}

Back off, and carry your idempotencyKey into the retry. A backoff loop that mints a new key on every attempt is how one slow request becomes fifty builds.