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.
Per-minute limits
| Endpoint | Per credential |
|---|---|
POST /v1/orders | 60 / minute |
GET /v1/orders/:id | 600 / minute |
GET /v1/events | 120 / minute |
Daily ceilings
These are the ones that actually stop you, and they reset at UTC midnight. Approval moves you up a tier.
| Ceiling | Sandbox | Production |
|---|---|---|
| Users provisioned, per day | 200 | 500 |
| Order builds, per day | 50 | 5,000 |
| Order builds, per user per day | 25 | 25 |
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.