API reference
Five endpoints. Base URL https://api.layout.link/v1, bearer auth on every call, JSON in and out.
POST /v1/users
Create someone you can order for. You send a name and a phone number; you get back an id to use everywhere else, and the link that person uses to claim the account.
curl https://api.layout.link/v1/users \
-H "Authorization: Bearer $LAYOUT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "firstName": "Dana", "lastName": "Whitfield", "phone": "+19165550142" }'
201 Created
{
"id": "usr_4b8e",
"status": "provisioned",
"session": { "scope": "build", "expiresIn": 3600 },
"handoffUrl": "https://account.layout.link/join"
}
| Field | Required | Notes |
|---|---|---|
firstName | Yes | Shown to the person on the consent screen. |
lastName | Yes | Shown to the person on the consent screen. |
phone | Yes | E.164, e.g. +19165550142. They verify it themselves on the link. |
Calling this twice with the same phone number gives you the same user back rather than a duplicate.
POST /v1/orders
Start building a cart. This returns immediately with building — carts take a while, because Layout is driving the restaurant's own site. Wait for the order.carted webhook, or poll.
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": "cart_20260912_8842"
}'
201 Created
{
"id": null,
"state": "building",
"idempotencyKey": "cart_20260912_8842"
}
id is null until there is an order to point at. Hold the idempotencyKey: it is your handle on this build until the id arrives.
| Field | Required | Notes |
|---|---|---|
userId | Yes | From POST /v1/users. |
query | Yes | What they asked for, in their words. Keep every size and prep detail: “grande iced latte, oat milk, extra ice”, not “latte”. |
placeId | One of three | A specific store. Otherwise send near or geo. |
near | One of three | An address, city or area, as text. |
geo | One of three | { "lat": 38.58, "lng": -121.49 }. |
items | No | The same order split one entry per item. Lets Layout tell you by name if one did not make it into the cart. |
modifiers | No | Customizations as an explicit checklist, on top of query. |
idempotencyKey | No, but send it | Retrying without one builds the order twice. See Rate limits and idempotency. |
A retry carrying a key you already used returns 200 with "replay": true and the original order, instead of building anything.
GET /v1/orders/:id
Everything Layout will tell you about one order.
curl https://api.layout.link/v1/orders/ord_7c21 \ -H "Authorization: Bearer $LAYOUT_SECRET"
200 OK
{
"publicId": "ord_7c21",
"state": "placed",
"store": "Sweetgreen, Market St",
"items": 2,
"totalMinor": 2140,
"currency": "usd",
"createdAt": "2026-09-12T17:00:41Z",
"placedAt": "2026-09-12T17:03:02Z",
"failureCode": null
}
Totals are minor units: 2140 is $21.40. An order belonging to another application is a 404, never a 403 — you cannot tell the difference between someone else's order and one that never existed, which is the point.
States
| State | Meaning |
|---|---|
building | Layout is on the restaurant's site assembling the cart. |
carted | Priced and ready. Show the person the total and send them to the link. |
placing | They confirmed. Submission is in flight. |
placed | It reached the merchant, with evidence. The only state that means the food is coming. |
failed | It did not go through. failureCode says why. Nothing was charged. |
unconfirmed | Layout cannot tell either way. Do not retry and do not say “failed”. |
refunded | Money went back. |
POST /v1/users/:id/grant
Mint a build-only token for one person, for an MCP session. Ask for it deliberately, when you are about to hand an assistant the wheel.
curl -X POST https://api.layout.link/v1/users/usr_4b8e/grant \ -H "Authorization: Bearer $LAYOUT_SECRET"
201 Created
{
"token": "lgb_9f3a2c…",
"expiresAt": "2026-09-12T18:00:41Z",
"scope": "build"
}
The token builds carts and reads state. It cannot add a card, confirm, or spend anything — all of that happens on Layout's own page, in front of the person whose money it is.
GET /v1/events
The same events your webhooks receive, as a cursor-paged feed. Useful when your endpoint was down, or when you would rather poll than run one.
curl "https://api.layout.link/v1/events?limit=50" \ -H "Authorization: Bearer $LAYOUT_SECRET"
200 OK
{
"events": [
{
"event": "order.placed",
"delivery_id": "dl_9f3a2c",
"order": { "id": "ord_7c21", "state": "placed", "store": "Sweetgreen, Market St",
"items": 2, "total_minor": 2140, "currency": "usd" },
"user": { "id": "usr_4b8e" },
"sent_at": "2026-09-12T17:03:02Z"
}
],
"nextCursor": "ev_01J8Z…"
}
Pass nextCursor back as ?cursor= to walk backwards. limit defaults to 50 and caps at 100. When nextCursor is null you have reached the end.