# Overview

## Layer REST API (v2) 1.0.0

REST API for AI-powered game asset creation on the Layer platform.

## Authentication

Every endpoint requires a Bearer token in the `Authorization` header, except the ones a client reads while deciding whether to integrate at all: `openapi.json`, `versioning`, and the interactive docs.

```
Authorization: Bearer <token>
```

### Personal Access Tokens (recommended)

The simplest way to authenticate is with a Personal Access Token (PAT):

1. Log in to [app.layer.ai](https://app.layer.ai)
2. Go to **Settings > Personal Access Tokens**
3. Click **Create Token**, give it a name, and copy the token value
4. Pass it as `Authorization: Bearer <your-pat-token>`

PATs are long-lived and ideal for scripts, CI pipelines, and partner integrations.

### OAuth2 (browser-based)

For browser-based applications, use the Auth0 OAuth2 flow. Redirect users to the Layer login page, exchange the authorization code for a JWT access token, and use it as `Authorization: Bearer <jwt>`.

## Creative Units

Generations consume Creative Units (CUs). Use the estimate endpoints to check cost before executing. Check workspace balance via `GET /v1/workspaces/{id}`.

## Pagination

List endpoints support cursor-based pagination via `limit` and `cursor` query parameters. Responses include a `pagination` object with `next_cursor`, `has_more_results`, and `total_count`. Cursors are opaque — do not parse or construct them.

## Errors

All errors return [RFC 9457 Problem Details](https://www.rfc-editor.org/rfc/rfc9457) with content type `application/problem+json`:

```
{
  "type": "https://api.layer.ai/errors/ERROR_CODE",
  "title": "Human-readable title",
  "status": 404,
  "detail": "Detailed description of what went wrong."
}
```

Branch on `type`, not on `title` or `detail` — those are prose and may be reworded. `type`is a stable opaque identifier whose last path segment is the machine-readable error code; it is not a live URL and does not resolve to a page. Documentation lives at [docs.layer.ai](https://docs.layer.ai).

Common status codes: `401` (unauthenticated), `403` (forbidden), `404` (not found), `422` (invalid input), `429` (rate limited).

Creative Units are not checked when a run is submitted, so an underfunded workspace does not fail at submission time. Call the estimate endpoint and read `has_sufficient_creative_units` to check up front; otherwise the run is accepted and then reaches a terminal `FAILED` state with `error_code` `INSUFFICIENT_BALANCE`.

## Idempotency

A lost response is indistinguishable from a request that never arrived, and retrying a generation spends Creative Units again. Send an idempotency key on any unsafe request and a retry is free:

```
Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7
```

The first request with a given key executes. Every later request with the same key returns that first response, with `Idempotent-Replayed: true` added, without executing anything. Any opaque string up to 255 characters works; a UUID per logical operation is the usual choice. Keys are scoped to your credential’s owner and replayable for 24 hours.

Three cases are not replays:

* **A different request under the same key** returns `422` `IDEMPOTENCY_KEY_REUSED`. The key covers the method, path, and body it was first used with, so this is a client bug rather than a retry — returning some other request’s result would be worse than an error.
* **A retry while the original is still running** returns `409` `IDEMPOTENT_REQUEST_IN_PROGRESS` with `Retry-After`, because the result does not exist yet.
* **A failed request** does not reserve its key. Nothing was spent, so a retry executes rather than replaying the error.

Keys on safe methods are ignored — `GET` is already repeatable.

## Rate limits

Quotas are per user and apply to the compute operations that spend Creative Units: starting a generation and starting a training run.

Every response describes the quotas in force, including one that rejected you for having no credential — a client that has not authenticated yet is the one that most needs to know what budget it will be planning against:

```
RateLimit-Policy: "generation";q=60;w=60, "generation-video";q=30;w=60, "training";q=10;w=3600
```

A quota named after a modality narrows the one it extends: a video generation spends from both `generation` and `generation-video`, so the tighter of the two decides.

Once you are authenticated, responses from the operations a quota governs also report what is left of it:

```
RateLimit: "generation";r=57;t=3
RateLimit-Limit: 60
RateLimit-Remaining: 57
RateLimit-Reset: 3
```

`RateLimit-Policy` and `RateLimit` are the standard structured fields — `q` is the quota, `w` the window in seconds, `r` the units left, `t` the seconds until the quota is fully restored. The `RateLimit-Limit`/`-Remaining`/`-Reset` triple repeats the tightest quota for clients that parse the older convention.

Exceeding a quota returns `429` with a `Retry-After` header; wait that many seconds before retrying.

## Versioning and deprecation

The API version is the first path segment. `/v1` and `/v2` are both stable and served together; v1 routes are unaffected by v2.

`/v2` is a complete surface — every resource is reachable under it, so a client can pin `/v2` and never fall back. It differs from v1 in the generation surface: inference runs take reference sets — trained subjects, styles, and characters — alongside a base model, and the v1 `/models` catalog is split into `/base-models` and `/reference-sets`.

Every other resource is the same endpoint under both prefixes: identical handler, request body, and response shape. Their v2 operation ids carry a `V2` suffix so generated clients get one symbol per published path.

Backward-compatible additions — a new endpoint, a new optional request field, a new response field — ship into an existing version without notice. Anything that could break a client gets a new version instead.

Nothing is withdrawn without notice. When an operation or a version is deprecated:

1. It is marked `deprecated: true` in `openapi.json`.
2. Every response from it carries `Deprecation`([RFC 9745](https://www.rfc-editor.org/rfc/rfc9745)) with the date it was deprecated, `Sunset` ([RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)) with the earliest date it may stop working, and a `Link` header with `rel="deprecation"` to this policy and `rel="successor-version"` to its replacement.
3. It keeps working until the sunset date, which is at least 180 days after the deprecation date.

Poll `openapi.json` or watch for `Deprecation` on your own traffic — both carry the same timeline.

Information

* OpenAPI version: `3.1.0`

## Operations

GET

[/v2/workspaces/{workspace\_id}/base-models](/docs/v2/rest-api/operations/listbasemodels)

GET

[/v2/workspaces/{workspace\_id}/base-models/defaults](/docs/v2/rest-api/operations/getbasemodeldefaults)

GET

[/v2/workspaces/{workspace\_id}/base-models/{base\_model\_id}](/docs/v2/rest-api/operations/getbasemodel)

GET

[/v2/workspaces/{workspace\_id}/base-models/{slug}/inference-schema](/docs/v2/rest-api/operations/getinferenceschema)

POST

[/v2/workspaces/{workspace\_id}/base-models/{slug}/inferences](/docs/v2/rest-api/operations/runinference)

GET

[/v2/workspaces/{workspace\_id}/reference-sets](/docs/v2/rest-api/operations/listreferencesets)

GET

[/v2/workspaces/{workspace\_id}/reference-sets/{reference\_set\_id}](/docs/v2/rest-api/operations/getreferenceset)

GET

[/v2/workspaces/{workspace\_id}/base-models/{slug}/inference-schema](/docs/v2/rest-api/operations/getinferenceschema)

POST

[/v2/workspaces/{workspace\_id}/base-models/{slug}/inferences](/docs/v2/rest-api/operations/runinference)

POST

[/v2/workspaces/{workspace\_id}/inferences/{inference\_id}/cancel](/docs/v2/rest-api/operations/cancelinferencev2)

POST

[/v2/workspaces/{workspace\_id}/inferences/estimate](/docs/v2/rest-api/operations/estimateinferencepricev2)

POST

[/v2/workspaces/{workspace\_id}/inferences](/docs/v2/rest-api/operations/executeinferencev2)

GET

[/v2/workspaces/{workspace\_id}/inferences/{inference\_id}](/docs/v2/rest-api/operations/getinferencerunv2)

POST

[/v2/workspaces/{workspace\_id}/reference-sets](/docs/v2/rest-api/operations/createreferencesetv2)

POST

[/v2/workspaces/{workspace\_id}/training-runs/estimate](/docs/v2/rest-api/operations/estimatetrainingpricev2)

GET

[/v2/workspaces/{workspace\_id}/training-runs](/docs/v2/rest-api/operations/listtrainingrunsv2)

POST

[/v2/workspaces/{workspace\_id}/training-runs](/docs/v2/rest-api/operations/starttrainingrunv2)

GET

[/v2/workspaces/{workspace\_id}/training-runs/{training\_run\_id}](/docs/v2/rest-api/operations/gettrainingrunv2)

POST

[/v2/workspaces/{workspace\_id}/training-runs/{training\_run\_id}/cancel](/docs/v2/rest-api/operations/canceltrainingrunv2)

GET

[/v2/workspaces/{workspace\_id}/training-runs/{training\_run\_id}/model](/docs/v2/rest-api/operations/gettrainedmodelv2)

POST

[/v2/workspaces/{workspace\_id}/files/upload-url](/docs/v2/rest-api/operations/requestfileuploadurlv2)

GET

[/v2/workspaces/{workspace\_id}/scoring-rules](/docs/v2/rest-api/operations/listscoringrulesv2)

POST

[/v2/workspaces/{workspace\_id}/scores](/docs/v2/rest-api/operations/scorefilesv2)

GET

[/v2/workspaces/{workspace\_id}/files/{file\_id}/scores](/docs/v2/rest-api/operations/getfilescoresv2)

GET

[/v2/workspaces/{workspace\_id}/workflows](/docs/v2/rest-api/operations/listworkflowsv2)

POST

[/v2/workspaces/{workspace\_id}/workflows/{workflow\_id}/estimate](/docs/v2/rest-api/operations/estimateworkflowpricev2)

POST

[/v2/workspaces/{workspace\_id}/workflows/{workflow\_id}/runs](/docs/v2/rest-api/operations/executeworkflowv2)

GET

[/v2/workspaces/{workspace\_id}/workflows/{workflow\_id}/runs/{run\_id}](/docs/v2/rest-api/operations/getworkflowrunv2)

GET

[/v2/workspaces](/docs/v2/rest-api/operations/listworkspacesv2)

GET

[/v2/workspaces/{workspace\_id}](/docs/v2/rest-api/operations/getworkspacev2)

GET

[/v2/workspaces/{workspace\_id}/usage](/docs/v2/rest-api/operations/getworkspaceusagev2)

GET

[/v2/workspaces/{workspace\_id}/projects](/docs/v2/rest-api/operations/listprojectsv2)

POST

[/v2/workspaces/{workspace\_id}/projects](/docs/v2/rest-api/operations/createprojectv2)

GET

[/v2/workspaces/{workspace\_id}/projects/{project\_id}](/docs/v2/rest-api/operations/getprojectv2)

DELETE

[/v2/workspaces/{workspace\_id}/projects/{project\_id}](/docs/v2/rest-api/operations/deleteprojectv2)

PATCH

[/v2/workspaces/{workspace\_id}/projects/{project\_id}](/docs/v2/rest-api/operations/updateprojectv2)

POST

[/v2/workspaces/{workspace\_id}/projects/{project\_id}/members](/docs/v2/rest-api/operations/addprojectmembersv2)

DELETE

[/v2/workspaces/{workspace\_id}/projects/{project\_id}/members/{user\_id}](/docs/v2/rest-api/operations/removeprojectmemberv2)

GET

[/v2/workspaces/{workspace\_id}/members](/docs/v2/rest-api/operations/listworkspacemembersv2)

POST

[/v2/workspaces/{workspace\_id}/members](/docs/v2/rest-api/operations/inviteworkspacememberv2)

PATCH

[/v2/workspaces/{workspace\_id}/members/{user\_id}](/docs/v2/rest-api/operations/updateworkspacememberv2)

GET

[/v2/workspaces/{workspace\_id}/groups](/docs/v2/rest-api/operations/listgroupsv2)

POST

[/v2/workspaces/{workspace\_id}/groups](/docs/v2/rest-api/operations/creategroupv2)

DELETE

[/v2/workspaces/{workspace\_id}/groups/{group\_id}](/docs/v2/rest-api/operations/deletegroupv2)

PATCH

[/v2/workspaces/{workspace\_id}/groups/{group\_id}](/docs/v2/rest-api/operations/updategrouprolev2)

GET

[/v2/workspaces/{workspace\_id}/groups/{group\_id}/members](/docs/v2/rest-api/operations/listgroupmembersv2)

POST

[/v2/workspaces/{workspace\_id}/groups/{group\_id}/members](/docs/v2/rest-api/operations/addgroupmemberv2)

DELETE

[/v2/workspaces/{workspace\_id}/groups/{group\_id}/members/{user\_id}](/docs/v2/rest-api/operations/removegroupmemberv2)

PUT

[/v2/workspaces/{workspace\_id}/groups/{group\_id}/usage-limit](/docs/v2/rest-api/operations/setgroupusagelimitv2)

DELETE

[/v2/workspaces/{workspace\_id}/groups/{group\_id}/usage-limit](/docs/v2/rest-api/operations/removegroupusagelimitv2)

## Authentication

### bearerAuth

Bearer authentication with a Layer Personal Access Token (PAT), prefixed `pat_`. Send it as `Authorization: Bearer <token>`. Create one in the app under Settings → Personal Access Tokens. An Auth0 session JWT is also accepted for first-party browser use.

**Security scheme type:** http

### oauth2

OAuth 2.0 authorization against Layer’s authorization server. Discovery metadata for this resource is published at `https://api.app.layer.ai/.well-known/oauth-protected-resource` (RFC 9728), and the authorization server’s own metadata at `https://auth.app.layer.ai/.well-known/oauth-authorization-server` (RFC 8414).

**Security scheme type:** oauth2

**Flow type:** authorizationCode

**Authorization URL:** <https://auth.app.layer.ai/authorize>

**Token URL:** <https://auth.app.layer.ai/oauth/token>

**Refresh URL:** <https://auth.app.layer.ai/oauth/token>

**Flow type:** clientCredentials

**Token URL:** <https://auth.app.layer.ai/oauth/token>

**Refresh URL:** <https://auth.app.layer.ai/oauth/token>
