# Quickstart

Your first sandbox order over REST, end to end, in about five minutes. No order you confirm here reaches a restaurant or charges anyone. New to Layout? [How Layout works](https://developer.layout.link/reference/how-it-works) shows the whole lifecycle on one page.

## 1. Get your credentials

Create an application in the [console](https://developer.layout.link/console/applications) and copy its sandbox secret from the [Credentials](https://developer.layout.link/console/credentials) page. Keep the secret server-side. Never ship it in a browser or an app binary.

A new application gets sandbox keys straight away. Production is reviewed by a person, and your sandbox keys keep working whatever that review decides. See [Environments and approval](https://developer.layout.link/reference/environments).

```bash
export LAYOUT_SECRET_KEY="sk_test_…"
```

The [Layout CLI](https://developer.layout.link/reference/cli) reads the same variable. `layout test-order` runs the sandbox steps below in one command, so you can see it work before you write any code.

## 2. Provision a user

A name and a phone number. That is the whole signup you have to run.

```bash
curl https://api.layout.link/v1/users \
  -H "Authorization: Bearer $LAYOUT_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "firstName": "Dana", "lastName": "Whitfield", "phone": "+15005550142" }'
# -> 201 { "id": "usr_4b8e9a01c2d3", "status": "provisioned", "next": "verify", … }
```

In sandbox the phone must be a reserved test number, `+1 500 555` followed by four digits. The answer's `id` is the person's `usr_` id, used below. `next` says how to connect them: see [The next step](https://developer.layout.link/reference/provisioning#next-step).

## 3. Verify the number

In production the person proves the number before you can confirm for them: Layout texts a code, they read it back to you, you send it. In sandbox nothing is texted and the code is always `000000`. Optional in sandbox.

```bash
curl https://api.layout.link/v1/users/usr_4b8e9a01c2d3/verify/start \
  -H "Authorization: Bearer $LAYOUT_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

curl https://api.layout.link/v1/users/usr_4b8e9a01c2d3/verify \
  -H "Authorization: Bearer $LAYOUT_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "code": "000000" }'
# -> 200 { "id": "usr_4b8e9a01c2d3", "verified": true, "authorized": true, … }
```

In production a good code makes the person's Layout account, and the answer reads `"status": "active"`. A person who signs in to Layout on this number themselves connects through a link they approve instead. See [Someone who signs in to Layout](https://developer.layout.link/reference/provisioning#connecting-an-existing-account). Before the first code reaches them, tell them about Layout in your own words: see [Introduce Layout first](https://developer.layout.link/reference/provisioning#introduce-layout-first).

## 4. Add a card

In production, mint a [card link](https://developer.layout.link/reference/card-links) and send the person to it. Sandbox needs no card.

```bash
curl https://api.layout.link/v1/users/usr_4b8e9a01c2d3/links \
  -H "Authorization: Bearer $LAYOUT_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "purpose": "card" }'
# -> 201 { "url": "https://layout.link/h/…", "expiresAt": "…", "purpose": "card" }
```

## 5. Mint a build grant

Carts act for one person, so they take that person's credential, never your secret. A build grant is one.

```bash
curl -X POST https://api.layout.link/v1/users/usr_4b8e9a01c2d3/grant \
  -H "Authorization: Bearer $LAYOUT_SECRET_KEY"
# -> 201 { "token": "lgb_…", "expiresAt": "…", "scope": "build" }

export LAYOUT_GRANT="lgb_…"
```

A grant lasts 15 minutes. To keep going, send the current token back as `{ "refresh": "lgb_..." }` before it expires. See [POST /v1/users/:id/grant](https://developer.layout.link/reference/api#post-v1-users-id-grant).

## 6. Build a cart

Send what the person asked for in their own words, and the store. Use a fresh random `idempotencyKey` for every new cart.

```bash
curl https://api.layout.link/v1/carts \
  -H "Authorization: Bearer $LAYOUT_GRANT" \
  -H "Content-Type: application/json" \
  -d '{ "request": "a large latte with oat milk", "placeId": "test_place_carted",
        "idempotencyKey": "7f3c2a9e-cart-1" }'
# -> 202 { "cart": { "id": "crt_0d928d04f1iQbL1AV4iXSKw2wabMk_B-", "status": "building" } }

export CART_ID="crt_0d928d04f1iQbL1AV4iXSKw2wabMk_B-"
```

`test_place_carted` is a [sandbox test restaurant](https://developer.layout.link/reference/sandbox#test-restaurants) with a fixed menu: it builds a ready cart at once, so this total is the same every time. A real store builds the same way in sandbox, from the menu Layout holds, with no browser. Its `placeId` comes from [GET /v1/places](https://developer.layout.link/reference/api#get-v1-places), searched with the person's device coordinates so the nearest store comes first. See [Finding the nearest store](https://developer.layout.link/reference/carts#nearest-store).

Read the cart every 2 to 3 seconds until it is no longer `building`:

```bash
curl https://api.layout.link/v1/carts/$CART_ID \
  -H "Authorization: Bearer $LAYOUT_GRANT"
# -> 200 { "cart": { "status": "ready", "totalMinor": 816, "items": […], "store": {…}, "codeRequired": false, … } }
```

`ready` means priced and confirmable for 15 minutes. `needs_answer` means the restaurant needs a choice, and `failed` means it could not be built. See [GET /v1/carts/:id](https://developer.layout.link/reference/carts#get-v1-carts-id).

## 7. Confirm

Show the person the cart's `totalMinor`, items and store, and confirm only after they say yes. Send `totalMinor` back unchanged as `expectedTotalMinor`: for this request the sandbox returns 816 (a $7.00 large oat-milk latte, $0.61 tax and Layout's $0.55 service fee); use the number your cart returned.

```bash
curl https://api.layout.link/v1/carts/$CART_ID/confirm \
  -H "Authorization: Bearer $LAYOUT_GRANT" \
  -H "Content-Type: application/json" \
  -d '{ "expectedTotalMinor": 816, "idempotencyKey": "7f3c2a9e-confirm-1" }'
# -> 202 { "order": { "id": "ord_7c21e4b9a0d3", "status": "placing", "simulated": true } }
```

In sandbox the confirm is a simulation: no restaurant receives it, no money moves, and nobody is texted. In production the same call places a real order. The person needs a verified number and a card from a [card link](https://developer.layout.link/reference/card-links). If [a code is due](https://developer.layout.link/reference/how-it-works#when-a-code-is-due), the answer is `400 code_required`: send the same request again with their `code`. On a timeout, retry with the same `idempotencyKey`.

## 8. Read the order

```bash
curl https://api.layout.link/v1/orders/ord_7c21e4b9a0d3 \
  -H "Authorization: Bearer $LAYOUT_SECRET_KEY"
# -> 200 { "id": "ord_7c21e4b9a0d3", "state": "placed", "simulated": true, … }
```

`state` ends `placed`, `failed` or `unconfirmed`. `unconfirmed` means Layout cannot tell whether the restaurant received the order: never treat it as failed, and never build it again. Order fields are snake_case, and the order's `total_minor` leaves out Layout's service fee. Pickup details can arrive later, each with `order.updated`. See [GET /v1/orders/:id](https://developer.layout.link/reference/api#get-v1-orders-id).

## 9. Get webhooks

Rather than polling, add a subscription on the console's [Webhooks](https://developer.layout.link/console/webhooks/subscriptions) page with an HTTPS URL. Layout then POSTs a signed event as the order moves: `order.carted` when the cart is ready, and `order.placed`, `order.failed` or `order.unconfirmed` at the end. Fire one at your endpoint from sandbox to test it:

```bash
curl https://api.layout.link/v1/sandbox/webhooks/fire \
  -H "Authorization: Bearer $LAYOUT_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "event": "order.placed" }'
```

Verify every delivery's signature with your sandbox signing secret. See [Webhooks](https://developer.layout.link/reference/webhooks) and [Event types](https://developer.layout.link/reference/events).

## Over MCP instead

MCP is the other door to the same ordering, with the same checks. Steps 1 to 5 do not change. Then connect the same `lgb_` token to `https://mcp.layout.link` as a bearer token, with no OAuth step, and call the `order` tool: `preview` to resolve the store, `build`, `status` until `carted`, then `confirm`. A build grant's MCP session has three tools, `order`, `order_status` and `places`, and in sandbox also `menu`. See [Ordering over MCP](https://developer.layout.link/reference/mcp), and [Two doors, one order](https://developer.layout.link/reference/how-it-works#two-doors) for how each REST call and state maps to MCP.

`DELETE /v1/users/:id` disconnects a person. See [Disconnect a person](https://developer.layout.link/reference/provisioning#disconnect-a-person).
