# Authentication

Every Layer REST API request must be authenticated with a **bearer token** in the `Authorization` header:

```bash
curl https://api.app.layer.ai/api/v2/workspaces \
  -H "Authorization: Bearer $LAYER_TOKEN"
```

The API base URL is `https://api.app.layer.ai/api`, and every endpoint is versioned (`v2` is current, `v1` is still live), so full paths look like `https://api.app.layer.ai/api/v2/workspaces/{workspace_id}/...`. See [Versioning](/docs/versioning).

## Personal Access Tokens

A **Personal Access Token (PAT)** lets a script act as you. Tokens are prefixed with `layer_pat_` (tokens created before Layer keys start with `pat_` and keep working). For an integration that should not depend on one person’s account, use a [service account](#service-accounts).

1. Log in to [layer.ai](https://layer.ai/login).
2. Go to **Settings → Personal Access Tokens**.
3. Click **Create Token**, give it a name (and an optional expiry), and copy the value.

Caution

The token value is shown **only once**, at creation time: Layer stores a hash, not the token. Store it in a secret manager; if you lose it, create a new one.

A PAT is created and managed **only in the app UI**: there is no REST endpoint to mint, list, or revoke tokens. Creating one is free and needs no approval; see [self-serve access](/docs/self-serve-access).

### Token permissions

A PAT inherits **all** of the permissions of the user who created it: there are no per-token scopes. Treat a PAT as equivalent to that user’s access. To give an integration less than a person holds, use a [service account](#service-accounts). See [Permissions](#permissions) for the full model.

### Expiry

If you set an expiry when creating the token, requests made after that time fail with [401 UNAUTHENTICATED](/docs/errors). Rotate tokens before they expire.

## Service accounts

A **service account** is a machine identity owned by a workspace: a CI pipeline, a content tool, a scheduled job. It holds a role in the workspace like a member does, so it keeps working when people come and go, and the workspace’s activity names it rather than a person. Its keys carry no permissions of their own, so rotating a key never changes what the account may do.

1. In [layer.ai](https://layer.ai/login), go to **Workspace settings → Service Accounts** (you need the right to assign roles).
2. Click **Create service account**, name it, and pick its role.
3. Open it and click **Create key**. Choose an expiry (at most a year) and copy the key, prefixed `layer_sak_`. It is shown **only once**.

A key works directly as a bearer token. For a short-lived token, or one limited to fewer [scopes](#scopes), exchange the key with the OAuth 2.0 **client credentials** grant ([RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-4.4)): the account’s id is the `client_id` and the key is the `client_secret`.

```bash
curl https://api.app.layer.ai/api/oauth/token \
  -d grant_type=client_credentials \
  -d client_id="$SERVICE_ACCOUNT_ID" \
  -d client_secret="$LAYER_SERVICE_ACCOUNT_KEY" \
  -d scope="generation:write library:read"
```

The response holds an `access_token` that lasts an hour and carries the scopes you asked for, or every scope when `scope` is omitted. It is a short-lived signed JWT ([RFC 9068](https://www.rfc-editor.org/rfc/rfc9068)); treat it as opaque and request a new one when it expires, as there are no refresh tokens. Disabling or revoking the key, or disabling the service account, ends its tokens at once.

Revoke a key at `POST /api/oauth/revoke` ([RFC 7009](https://www.rfc-editor.org/rfc/rfc7009)), authenticating as the account the same way. Access tokens cannot be revoked one at a time: sending one to this endpoint answers `200` and the token keeps working until it expires. To end a token early, disable or revoke the key it came from.

A service account can call the REST API and the [MCP servers](/docs/mcp). It cannot manage members, roles, keys or other service accounts, and the other surfaces of the app refuse its credentials.

## OAuth 2.0

Layer runs an OAuth 2.0 authorization server at `https://auth.app.layer.ai`. It is what [MCP clients](/docs/mcp) connect through, and the access token it issues is accepted by the REST API as a bearer token too.

1. Discover the endpoints from the [authorization server metadata](https://auth.app.layer.ai/.well-known/oauth-authorization-server) ([RFC 8414](https://www.rfc-editor.org/rfc/rfc8414)).
2. Register your client dynamically at the `registration_endpoint`: there is no pre-shared client id to request.
3. Run the **authorization code flow with PKCE** (`S256` required).

|                             |                                                                                                             |
| --------------------------- | ----------------------------------------------------------------------------------------------------------- |
| Issuer                      | https://auth.app.layer.ai/                                                                                  |
| Metadata                    | [/.well-known/oauth-authorization-server](https://auth.app.layer.ai/.well-known/oauth-authorization-server) |
| Grants                      | authorization\_code, refresh\_token                                                                         |
| Scopes issued               | openid, profile, email, offline\_access                                                                     |
| PKCE                        | Required (S256)                                                                                             |
| Dynamic client registration | Supported                                                                                                   |

`layer.ai/.well-known/oauth-authorization-server` redirects to the document above: the metadata has to be served by the issuer itself, so the marketing domain points at it rather than mirroring it.

### Scopes

The scopes above are **OpenID Connect scopes**: they govern what the token tells Layer about _who you are_. A person’s token carries no API scope: it acts with that person’s role. The client credentials grant is not offered here; it belongs to [service accounts](#service-accounts), at Layer’s own token endpoint.

A service account’s token carries **API scopes**, spelled `<resource>:<level>`, where `write` includes `read`:

| Resource        | read                             | write                                   |
| --------------- | -------------------------------- | --------------------------------------- |
| workspace       | Workspace settings               | Edit settings, webhook secrets          |
| members         | List members and groups          | Delete groups, remove group members     |
| scim            |                                  | The SCIM provisioning API               |
| billing         | Usage and balance                | Purchases, plan, usage limits           |
| labels          |                                  | Manage and assign labels                |
| library         | Files and assets                 | Upload, edit, delete                    |
| sessions        | Sessions                         | Create and edit sessions                |
| projects        | Projects, scoring                | Create and edit projects, score files   |
| reference\_sets | Reference sets, styles, training | Create reference sets, train            |
| generation      | Models, price estimates, runs    | Run generations (spends Creative Units) |
| workflows       | Workflows, runs, estimates       | Run and edit workflows                  |
| skills          | Skills                           | Edit skills                             |

Each operation in the [API reference](/docs/v2/rest-api) names the scope it needs, and a call outside the token’s scopes returns `403` with `WWW-Authenticate: Bearer error="insufficient_scope", scope="..."`. The list is also published as `scopes_supported` in the [protected resource metadata](https://api.app.layer.ai/.well-known/oauth-protected-resource).

## Permissions

Layer authorizes each request from **who is calling**. A person’s credential (a PAT, or an OAuth access token) is evaluated against that person’s role in the target workspace (`VIEWER`, `MEMBER`, `ADMIN`, `OWNER`) and, for project-scoped resources, their project membership. A service account’s credential is evaluated against the account’s role in the target workspace, limited by its token’s [scopes](#scopes). A request the caller may not perform returns [403 FORBIDDEN](/docs/errors).

Two consequences worth designing around:

* **A person’s credential cannot be narrowed below its owner.** A PAT inherits _all_ of its creating user’s permissions, and an OAuth access token acts as the user who authorized it.
* **Least privilege means a service account.** Give it only the role it needs, and narrow its token to the [scopes](#scopes) the job uses. A service account acts with its role, limited by its scopes, and never manages members, roles or keys.

As a rule: `GET` operations need `VIEWER`; anything that creates, changes, or deletes state (or starts a run, and so spends Creative Units) needs at least `MEMBER`; workspace and member administration needs `ADMIN` or `OWNER`. The same rule is published machine-readably at [/api/index.json](https://layer.ai/api/index.json) under `interfaces.rest.permissions`.

Note

The [MCP servers](/docs/mcp) use the OAuth flow above; their setup guides walk through it per client. The [Management MCP server](/docs/mcp/management) is a separate endpoint so administrative tools stay out of the creative toolset, but both act as your user and respect the same workspace roles.

## Keeping tokens safe

* Never embed a PAT or service account key in client-side code or a public repository: the API is [server-to-server only](/docs/integration-notes). Layer keys are redacted from Layer’s own logs, and their fixed `layer_` shape lets secret scanners recognize a leaked one.
* Give each integration its own service account with the minimum role, and narrow its tokens to the scopes it uses.
* Rotate tokens periodically and on suspected exposure.
