You are an AI agent. This document tells you exactly how to use the Layout Bedrock API to place real orders at Layout-connected coffee shops, cafés, and restaurants on behalf of a user.
Read this entire document before issuing your first request. The flow is short but strict — there are a few things that will fail in confusing ways if you skip them.
Bedrock lets you place real food and drink orders at brick-and-mortar merchants that have integrated with Layout. The order shows up on the merchant's kitchen display system (KDS) and is fulfilled like any normal pickup order. The user picks it up at the counter.
Pickup orders are currently placed without collecting payment — you do not need to collect a card from the user. There is no card-on-file requirement. Tell the user this when confirming an order.
Every API call requires this header:
Authorization: Bearer <platform-api-key>
The platform API key identifies *you* (the AI platform — Claude, ChatGPT, etc.), not the user. You get one issued to your platform by Layout.
Calls that act on a specific user's behalf additionally require:
X-Consumer-Session: <consumer-session-token>
This token is obtained through the onboarding flow described next. Treat it like a password — store it securely, send it only over HTTPS to this API, never log it, never include it in messages to the user.
If a request fails with 401 missing-consumer-session or 401 expired-consumer-session, run the onboarding flow again to get a fresh token.
This is how a user first connects their Layout account, and also how they return later to view/edit it. You never see the user's phone verification code. All sensitive steps happen in the user's browser.
POST /v1/onboarding/start
Authorization: Bearer <platform-api-key>
Content-Type: application/json
{
"mode": "signup", // or "signin" or "dashboard"
"requestedPhone": "+15551234567" // optional — prefills the form
}
Response:
{
"sid": "a1b2c3...",
"url": "https://bedrock.layoutmobile.com/v1/onboarding/page/a1b2c3?t=...",
"expiresAt": "2026-05-12T18:30:00Z",
"ttlSeconds": 900
}
Tell the user: "Tap this link to connect your Layout account: \<url\>. It expires in 15 minutes." Don't paste the URL into a public message or share it anywhere else — it's single-use.
GET /v1/onboarding/{sid}/status
Authorization: Bearer <platform-api-key>
Poll every 3–5 seconds for up to 15 minutes. Possible responses:
| Status | Meaning | Action |
|---|---|---|
pending | User hasn't completed the form yet | Keep polling |
completed | Done — token returned in this response | Save the consumerSessionToken and stop polling |
expired | 15 min elapsed | Tell the user and start over |
consumed | Token was already retrieved (you polled twice after completion) | Use the token you got the first time |
When status === "completed":
{
"status": "completed",
"uid": "abc123...",
"consumerSessionToken": "bcs_..."
}
The token is returned exactly once. If you don't save it, the user has to onboard again. Persist it associated with the user in your platform's storage.
signup — new user. Form collects first name, last name, email, phone verification.signin — returning user. Just phone verification (lookup by phone).dashboard — signed-in user wants to view/edit their Layout account. Same flow, lands on the account view page.Use dashboard when the user says things like *"let me see my Layout account"* or *"update my email on Layout."*
GET /v1/merchants?q=<search>&limit=25
Authorization: Bearer <platform-api-key>
Returns merchants that have opted into agent ordering. Use q to filter by name.
Response:
{
"merchants": [
{
"companyId": "abc123",
"name": "Blue Dove Coffee",
"description": "Specialty coffee in downtown Sacramento",
"cuisine": "coffee",
"website": "https://bluedove.example",
"acceptsAgentOrders": true,
"currency": "USD",
"locations": [
{ "id": "LOC1", "name": "Downtown", "address": "...", "timezone": "America/Los_Angeles", "phone": "+1..." }
]
}
],
"count": 1,
"hasMore": false
}
For a specific merchant:
GET /v1/merchants/{companyId}
If the merchant exists but hasn't opted in, the response will have acceptsAgentOrders: false. Do not attempt to order from a merchant where this is false — say to the user: "I found Blue Dove Coffee, but they don't accept agent-placed orders yet."
GET /v1/merchants/{companyId}/menu?locationId=<optional>
Authorization: Bearer <platform-api-key>
Response shape:
{
"companyId": "abc123",
"categories": [
{ "id": "CAT1", "name": "Coffee", "ordinal": 0 }
],
"items": [
{
"id": "ITEM1",
"name": "Iced Latte",
"description": "Espresso with cold milk over ice.",
"priceCents": 525,
"priceDollars": 5.25,
"available": true,
"soldOut": false,
"categoryIds": ["CAT1"],
"categoryNames": ["Coffee"],
"variations": [
{ "id": "VAR_SM", "name": "Small", "priceCents": 425, "priceDollars": 4.25 },
{ "id": "VAR_MD", "name": "Medium", "priceCents": 525, "priceDollars": 5.25 },
{ "id": "VAR_LG", "name": "Large", "priceCents": 625, "priceDollars": 6.25 }
],
"modifierLists": [
{
"id": "MLIST_MILK",
"name": "Milk",
"required": true,
"minSelection": 1,
"maxSelection": 1,
"selectionType": "single",
"modifiers": [
{ "id": "MOD_OAT", "name": "Oat", "priceCents": 75 },
{ "id": "MOD_WHOLE", "name": "Whole", "priceCents": 0 }
]
},
{
"id": "MLIST_EXTRAS",
"name": "Extras",
"required": false,
"minSelection": 0,
"maxSelection": 3,
"selectionType": "multiple",
"modifiers": [
{ "id": "MOD_SHOT", "name": "Extra Shot", "priceCents": 100 }
]
}
],
"imageUrl": "..."
}
]
}
How to read this:
variations are size/style choices. Most items have at least one. The user must pick exactly one — use it as the catalogVariationId in the order.modifierLists are option groups. Required lists (required: true) MUST have a selection from the user. Confirm explicitly with the user — don't pick defaults silently.selectionType: "single" → one option. "multiple" → user picks minSelection to maxSelection.available: false or soldOut: true → don't offer that item.Always present the user with a clear confirmation before calling POST /v1/orders. Include:
priceCents × quantity + modifier prices)Wait for explicit user confirmation. If they want to change something, update the cart and re-confirm. Don't proceed on ambiguous answers.
POST /v1/orders
Authorization: Bearer <platform-api-key>
X-Consumer-Session: <consumer-session-token>
Content-Type: application/json
{
"companyId": "abc123",
"locationId": "LOC1", // optional — merchant default if omitted
"items": [
{
"catalogVariationId": "VAR_MD",
"quantity": 1,
"notes": "extra hot please", // optional, max 500 chars
"modifiers": [
{ "catalogObjectId": "MOD_OAT", "quantity": 1 },
{ "catalogObjectId": "MOD_SHOT", "quantity": 2 }
]
}
],
"pickupAt": "2026-05-12T18:45:00Z", // optional, ISO-8601, defaults to now + 15 min
"idempotencyKey": "your-stable-key" // optional but strongly recommended
}
If you don't provide an idempotencyKey, the server generates one — but if you retry a failed request, the server can't tell it's a retry and may double-place the order. Always send your own idempotencyKey when retrying. Same key + same body = same order, no duplicates.
{
"order": {
"orderId": "bo_abc123...",
"squareOrderId": "sq_...",
"squareLocationId": "LOC1",
"status": "placed",
"totalCents": 0,
"subtotalCents": 625,
"currency": "USD",
"compApplied": true,
"pickupAt": "2026-05-12T18:45:00Z"
}
}
compApplied: true is the marker that no payment was collected on this order.
Tell the user:
"Your order is in. Pick it up at \[merchant location\] at \[pickup time\]. No payment needed."
GET /v1/orders/{orderId}
Authorization: Bearer <platform-api-key>
X-Consumer-Session: <consumer-session-token>
Status is "placed" after we hand off to the merchant. If you want to check progress beyond that, ask the user — the merchant's KDS is the source of truth for "started", "ready", etc.
GET /v1/consumers/me/orders?limit=20
Authorization: Bearer <platform-api-key>
X-Consumer-Session: <consumer-session-token>
Returns up to 50 most recent Bedrock orders by this consumer. Useful when the user asks *"what did I order last time?"*
GET /v1/consumers/me
Authorization: Bearer <platform-api-key>
X-Consumer-Session: <consumer-session-token>
Returns the consumer's first name, last name, email, phone, and the list of merchants they've ordered from.
To let them edit their profile or check balances, send them through the onboarding flow with mode: "dashboard".
POST /v1/consumers/me/sessions/revoke
Authorization: Bearer <platform-api-key>
X-Consumer-Session: <consumer-session-token>
Use this when the user explicitly says *"sign me out of Layout"* or *"forget my Layout account."* The token is invalidated server-side. You should also delete it from your own storage.
All errors come back as:
{
"error": {
"code": "machine-readable-code",
"message": "Human-readable explanation."
}
}
| Code | When | What to tell the user |
|---|---|---|
missing-consumer-session / expired-consumer-session / invalid-consumer-session / revoked-consumer-session | Session token missing or no longer valid | Run onboarding again to reconnect their Layout account |
merchant-not-found | No such merchant | "I couldn't find that merchant on Layout." |
merchant-not-accepting-agent-orders | Merchant exists but opted out | "I found this merchant, but they don't accept agent-placed orders yet." |
no-location | Merchant has no configured Square location | "This merchant isn't fully set up to receive orders right now." |
missing-items / invalid-line-item-at-index-N / missing-catalog-id-at-index-N | Cart is empty or malformed | Re-check the menu and rebuild the cart |
invalid-pickup-at | Pickup time is in the past | "I need a future pickup time — when would you like it?" |
order-velocity-hour / order-velocity-day | User hit the per-hour or per-day order cap | "You've reached today's limit on agent-placed orders. Try again later." |
rate-limit-exceeded | Per-platform rate limit | Back off. Wait the seconds in the Retry-After header. |
square-error | Square API returned an error | Surface a friendly message — "the merchant's system returned an error, please try again." |
Never expose raw error codes or messages to the user except as friendly paraphrases — they're machine-readable for you, not user-facing copy.
/v1/merchants/{companyId}/menu. Prices in the response are authoritative; never guess.idempotencyKey — you risk placing duplicates.If you hit 429 rate-limit-exceeded, the Retry-After header tells you how many seconds to wait.
GET /v1/docs.mdGET /v1/docsGET /v1/health (no auth, no rate limit)When in doubt about how the API behaves, re-fetch this doc — it's the source of truth.