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"
}
FieldRequiredNotes
firstNameYesShown to the person on the consent screen.
lastNameYesShown to the person on the consent screen.
phoneYesE.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.

FieldRequiredNotes
userIdYesFrom POST /v1/users.
queryYesWhat they asked for, in their words. Keep every size and prep detail: “grande iced latte, oat milk, extra ice”, not “latte”.
placeIdOne of threeA specific store. Otherwise send near or geo.
nearOne of threeAn address, city or area, as text.
geoOne of three{ "lat": 38.58, "lng": -121.49 }.
itemsNoThe same order split one entry per item. Lets Layout tell you by name if one did not make it into the cart.
modifiersNoCustomizations as an explicit checklist, on top of query.
idempotencyKeyNo, but send itRetrying 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

StateMeaning
buildingLayout is on the restaurant's site assembling the cart.
cartedPriced and ready. Show the person the total and send them to the link.
placingThey confirmed. Submission is in flight.
placedIt reached the merchant, with evidence. The only state that means the food is coming.
failedIt did not go through. failureCode says why. Nothing was charged.
unconfirmedLayout cannot tell either way. Do not retry and do not say “failed”.
refundedMoney 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.