# Sign in with Layout

One button that signs a person in with their Layout account and lets your app place orders for them, paid with the card they keep in Layout.

## Two ways in

There are two ways to connect a person to your app. This page covers the first.

| Way | What the person sees | Use it when |
| --- | --- | --- |
| [Sign in with Layout](#hosted) | Layout's own sign-in and approval page, in the system browser sheet. | You want the shortest build. Layout handles sign-in, the code and consent. |
| [Provisioning](https://docs.layout.link/provisioning) | Your own screens. Layout texts the person a code and they read it back to your app. | You already collect the person's phone number and want to stay on your screens. |

The two ways give the same person different ids, so pick one way for each person.

## Hosted

### 1. Create an OAuth client

On the console's [OAuth clients](https://console.layout.link/oauth-clients) page, add your redirect URIs. You get a `client_id`. It is a public client: authorization code with PKCE (S256), no client secret. See [OAuth clients](https://docs.layout.link/authentication#oauth-clients).

### 2. Add the button

Use the button as Layout ships it. Link it to your authorize URL.

```
<a href="/login/layout">
  <img src="https://docs.layout.link/brand/sign-in-with-layout/continue-black-48.svg"
       width="227" height="48" alt="Continue with Layout" />
</a>
```

On iOS and Android, use the PNGs at `@2x` and `@3x` as an image button. See [Button rules](#button-rules) and [Brand assets](#brand-assets).

### 3. Send them to Layout

```
https://api.layout.link/v1/oauth/authorize
  ?response_type=code
  &client_id=$CLIENT_ID
  &redirect_uri=https%3A%2F%2Fyourapp.example%2Fcallback
  &code_challenge=$CODE_CHALLENGE
  &code_challenge_method=S256
  &scope=order
  &resource=https%3A%2F%2Fapi.layout.link
  &state=$STATE
  &login_hint=%2B19165550142
```

| Parameter | Notes |
| --- | --- |
| `redirect_uri` | Must exactly match one you registered on the client. |
| `code_challenge` | Required. S256 only. |
| `code_challenge_method` | Required. `S256`. Leaving it out is `invalid_request`. |
| `scope` | `order`. |
| `resource` | `https://api.layout.link` for the REST API. Add `https://mcp.layout.link` as a second `resource` to cover both in one approval (see [One authorization for both](https://docs.layout.link/authentication#one-authorization-for-both)). |
| `state` | Comes back unchanged. Check it on your callback. |
| `prompt` | Optional. Leave it out and a person who already approved your app is sent straight back (see [Returning people](#returning-people)). `consent` always shows the approval page. `none` never shows a page: you get a code, or an `error` on your redirect URI (see [Returning people](#returning-people)). |
| `login_hint` | Optional. The person's phone number in E.164 (`+19165550142`), URL-encoded. Layout fills it in on its sign-in step when the person is not signed in yet. Only a `+1` number is filled in today; another E.164 number is accepted and left blank. It never sends a code by itself; the person still taps to get one. Anything that is not an E.164 number is ignored. |

**Open it in the system browser sheet, never an embedded web view.** On iOS that is `ASWebAuthenticationSession`, on Android a Custom Tab, on the web a full-page redirect. An embedded view cannot share the person's Layout sign-in, and the person cannot tell the page is really Layout's.

The person signs in with their phone and a code from Layout, and approves your app on Layout's page. It says, in Layout's words: *"{your app} will be able to place orders for you, paid with the card on file in your Layout account. You can disconnect {your app} anytime in Layout."* {your app} is the host of the redirect URI the request uses, such as `yourapp.example`, unless Layout has verified your client, when it is the client's name. Signing in does not ask for a card. Layout asks for one when the person confirms their first order, and the confirm shows the card that will be billed.

### 4. Exchange the code

Layout redirects to your `redirect_uri` with `code` and `state`, or with `error` when the person cancels (`access_denied`) or the request is refused. Exchange the code from your server:

```bash
curl https://api.layout.link/v1/oauth/token \
  -d grant_type=authorization_code \
  -d code=$CODE \
  -d redirect_uri=https://yourapp.example/callback \
  -d code_verifier=$CODE_VERIFIER \
  -d client_id=$CLIENT_ID \
  -d resource=https://api.layout.link
```

You get an access token (ten minutes) and a rotating refresh token (thirty days, renewed on every refresh). See [Tokens and refresh](https://docs.layout.link/authentication#tokens-and-refresh).

### 5. Learn who signed in

```bash
curl https://api.layout.link/v1/whoami \
  -H "Authorization: Bearer $LAYOUT_PERSON_TOKEN"

200 OK
{
  "kind": "user",
  "app": { "id": "lyt_app_3f9c2a7e1b4d8f6a0c5e9b27" },
  "environment": "live",
  "user": { "id": "usr_4b8e9a01c2d3" },
  "scopes": ["order"]
}
```

`user.id` is stable: the same person gets the same id in your app every time, on every device. Key your own account on it. It is yours alone; another app knows the same person by a different id. It is not the id `POST /v1/users` gives the same person (see [Two ways in](#two-ways-in)).

### Returning people

Send a returning person through the same authorize URL. If they are signed in to Layout in that browser and already approved your app for the same scope and resources, Layout sends them straight back with a code and shows nothing. Exchange it as before; `whoami` answers the same `user.id`. If they signed out of Layout everywhere, disconnected your app, or your request asks for more than they approved, they see the page again.

To check silently, for instance when your app starts, send `prompt=none`. You get a code, or one of these on your redirect URI, and the person sees no page:

| `error` | What to do |
| --- | --- |
| `login_required` | They are not signed in to Layout in this browser. Show the button. |
| `consent_required` | They are signed in but have not approved your app, or not for this. Show the button. |
| `temporarily_unavailable` | Layout could not check. Show the button, or retry. |
| `unauthorized_client` | Your application or developer account is suspended, or, for the MCP resource, the application was deleted. Showing the button will not help until that is fixed. |
| `invalid_target` | The `resource` is not one of Layout's, or this `client_id` does not belong to an application in your console. Check both. |

`prompt=none` works only with an `https` redirect URI: with a `localhost` one it always answers `consent_required`. Combined with another `prompt` value it is `invalid_request`.

### Sign out

When the person signs out of your app, revoke their connection with either token:

```bash
curl https://api.layout.link/v1/oauth/revoke \
  -d token=$REFRESH_TOKEN \
  -d client_id=$CLIENT_ID

200 OK
```

This disconnects your app, the same as the person doing it in Layout, so the next sign-in shows the approval page again. If you only want to forget the session in your app and keep the connection, drop your tokens and do not revoke. See [Revoking a connection](https://docs.layout.link/authentication#revoking-a-connection).

## Your own screens

To keep the person on your screens, provision them with `POST /v1/users` and verify the code Layout texts them. For a new number, a good code makes the person's Layout account, so there is no sign-up page. See [Verify their phone](https://docs.layout.link/provisioning#verify-their-phone).

A returning person on a new phone is verified the same way and keeps the same id. See [Verify their phone](https://docs.layout.link/provisioning#verify-their-phone) for `phone_changed` and `already_claimed`.

## Button rules

The button is Layout's, so a person recognises it in every app. Use it exactly as shipped.

- **Black or white.** Black on light backgrounds, white on dark or photographic ones. No other colour, including Layout blue.
- **Two labels.** *Continue with Layout* to sign in. *Order with Layout* where the tap starts an order. No other wording, and no translation of the word Layout.
- **Four sizes.** Pills 56, 48 and 40 pixels tall, and a 48-pixel circle with the symbol alone where space is tight. Scale nothing.
- **Nothing changed.** Not the colour, font, shape, corner radius or wording. No border, shadow or extra icon.

## Brand assets

SVG and PNG at `@1x`, `@2x` and `@3x`, at `https://docs.layout.link/brand/sign-in-with-layout/`. The label is drawn as shapes, so it looks the same with no font installed.

| File | Size | Button |
| --- | --- | --- |
| [continue-black-56](https://docs.layout.link/brand/sign-in-with-layout/continue-black-56.svg) | 258 × 56 |  |
| [continue-black-48](https://docs.layout.link/brand/sign-in-with-layout/continue-black-48.svg) | 227 × 48 |  |
| [continue-black-40](https://docs.layout.link/brand/sign-in-with-layout/continue-black-40.svg) | 206 × 40 |  |
| [continue-white-56](https://docs.layout.link/brand/sign-in-with-layout/continue-white-56.svg) | 258 × 56 |  |
| [continue-white-48](https://docs.layout.link/brand/sign-in-with-layout/continue-white-48.svg) | 227 × 48 |  |
| [continue-white-40](https://docs.layout.link/brand/sign-in-with-layout/continue-white-40.svg) | 206 × 40 |  |
| [order-black-56](https://docs.layout.link/brand/sign-in-with-layout/order-black-56.svg) | 231 × 56 |  |
| [order-black-48](https://docs.layout.link/brand/sign-in-with-layout/order-black-48.svg) | 203 × 48 |  |
| [order-black-40](https://docs.layout.link/brand/sign-in-with-layout/order-black-40.svg) | 184 × 40 |  |
| [order-white-56](https://docs.layout.link/brand/sign-in-with-layout/order-white-56.svg) | 231 × 56 |  |
| [order-white-48](https://docs.layout.link/brand/sign-in-with-layout/order-white-48.svg) | 203 × 48 |  |
| [order-white-40](https://docs.layout.link/brand/sign-in-with-layout/order-white-40.svg) | 184 × 40 |  |
| [circle-black-48](https://docs.layout.link/brand/sign-in-with-layout/circle-black-48.svg) | 48 × 48 |  |
| [circle-white-48](https://docs.layout.link/brand/sign-in-with-layout/circle-white-48.svg) | 48 × 48 |  |

Each name has a `.svg`, a `.png`, an `@2x.png` and an `@3x.png`, for example `continue-black-48@3x.png`. Give the image the size in the table and an `alt` of its label.
