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 | 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 | 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 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.
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 and 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). |
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). 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). |
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:
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.
5. Learn who signed in
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).
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:
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.
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.
A returning person on a new phone is verified the same way and keeps the same id. See 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 | 258 × 56 | |
continue-black-48 | 227 × 48 | |
continue-black-40 | 206 × 40 | |
continue-white-56 | 258 × 56 | |
continue-white-48 | 227 × 48 | |
continue-white-40 | 206 × 40 | |
order-black-56 | 231 × 56 | |
order-black-48 | 203 × 48 | |
order-black-40 | 184 × 40 | |
order-white-56 | 231 × 56 | |
order-white-48 | 203 × 48 | |
order-white-40 | 184 × 40 | |
circle-black-48 | 48 × 48 | |
circle-white-48 | 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.