# API keys (/docs/api/concepts/api-keys)



An API key authenticates every request you make and decides what that request is allowed to do. One key is scoped to one [organization](/docs/api/concepts/organizations). It carries the rate-limit tier that caps throughput, the allowlist of `returnUrl` values the OAuth flows will accept, and the scope list that gates individual routes.

## Key format [#key-format]

```text
lp_<env>_<keyid>_<secret>
```

The `env` segment is one of `live` or `test`:

* `lp_live_…` — production. Real platform calls, real credit burn, real OAuth.
* `lp_test_…` — sandbox. Deterministic fakes, no credit burn, no real platform calls. Identical response envelopes and signed webhooks. See [Sandbox](/docs/api/concepts/sandbox) for the full behavior contract.

The two are minted separately. Routes don't gate on env — the same scope set works on either — but the request envelope's behavior diverges as soon as a sandbox key is detected. Any prefix-vs-row env mismatch is rejected at auth time with `401 UNAUTHENTICATED`.

Treat the full key as an opaque secret beyond the env segment. Layers may expose a shortened key id in responses and audit logs; that id is safe to log, but the full key is not.

<Callout type="warn">
  The secret is shown exactly once - at the moment of creation. We can't recover it, display it again, or confirm you have the right one. Paste it into your secrets manager before you close the tab.
</Callout>

## Sending the key [#sending-the-key]

Send the key as `Authorization: Bearer <key>` on every request. This is the only accepted auth form.

```http
GET /v1/whoami HTTP/1.1
Host: api.layers.com
Authorization: Bearer lp_...
```

The first call any client should make is [`GET /v1/whoami`](/docs/api/reference/organizations/whoami) - it resolves your key to the org it's bound to and lets you fail fast if anything is wrong. Response shape:

```json
{
  "organizationId": "org_2481fa5c-a404-44ed-a561-565392499abc",
  "workspaceId": "org_2481fa5c-a404-44ed-a561-565392499abc",
  "organizationName": "Acme Growth",
  "scopes": [],
  "parentOrganizationId": null,
  "rateLimitTier": "standard",
  "apiKeyId": "key_c2037bb9-354d-4662-96b7-97a28ad6b6e1"
}
```

`parentOrganizationId` names the parent org a [child key](#child-keys-for-sub-organizations) reports to (or `null` for a top-level key); `scopes` is the key's granted scopes as a flat string array. See [whoami](/docs/api/reference/organizations/whoami#field-notes) for the full field reference.

A `200` from `/whoami` is itself the "key is live and the org is in good standing" signal. If access is suspended every endpoint - including this one - returns `503 KILL_SWITCH` with a request id; quote that id to your Layers contact.

## Scopes [#scopes]

<Callout>
  Scope enforcement is live and **deny-by-default**. A key is granted only the scopes minted onto it; calling an endpoint whose declared scope the key doesn't hold returns `403 FORBIDDEN_SCOPE`. **An empty scope list grants nothing** — every new key must be minted with at least one scope (see [Creating API keys](#creating-api-keys)). `/v1/whoami` returns whichever scopes were minted onto the key.
</Callout>

Every scope and the functionality it gates:

| Scope               | Functionality                                                                                                                                                                                                                                                                                        |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projects:read`     | List and read projects.                                                                                                                                                                                                                                                                              |
| `projects:write`    | Create and update projects.                                                                                                                                                                                                                                                                          |
| `ingest:write`      | Trigger GitHub / website / App Store ingestion jobs.                                                                                                                                                                                                                                                 |
| `content:read`      | List and read generated content.                                                                                                                                                                                                                                                                     |
| `content:write`     | Start content generation (slideshow / video / UGC remix).                                                                                                                                                                                                                                            |
| `content:approve`   | Approve or reject generated content.                                                                                                                                                                                                                                                                 |
| `social:read`       | List connected social accounts.                                                                                                                                                                                                                                                                      |
| `social:write`      | Connect, configure, and remove social accounts (OAuth flows).                                                                                                                                                                                                                                        |
| `publish:read`      | List scheduled posts and their status.                                                                                                                                                                                                                                                               |
| `publish:write`     | Schedule and publish content to connected accounts.                                                                                                                                                                                                                                                  |
| `events:read`       | Read the SDK analytics event stream.                                                                                                                                                                                                                                                                 |
| `events:read+pii`   | Read events **including** unredacted personally-identifiable fields. Grant only when the integration needs PII.                                                                                                                                                                                      |
| `events:write`      | Forward server-side events / CAPI relay (counts against the customer's analytics + relay).                                                                                                                                                                                                           |
| `metrics:read`      | Read organic and ads performance metrics.                                                                                                                                                                                                                                                            |
| `ads:read`          | Read ad accounts, campaigns, ad sets, ads, metrics, audit log, and the pending queue.                                                                                                                                                                                                                |
| `ads:write`         | Umbrella ads-write — OAuth init + ads-content override. Prefer the fine-grained [`ads:write:*` sub-scopes](#ads-sub-scopes) below for least-privilege keys.                                                                                                                                          |
| `bootstrap:write`   | Run the end-to-end marketing-bootstrap onboarding workflow.                                                                                                                                                                                                                                          |
| `media:read`        | List and read media-library assets.                                                                                                                                                                                                                                                                  |
| `media:write`       | Upload, finalize, and delete media-library assets.                                                                                                                                                                                                                                                   |
| `influencers:read`  | List and read influencers.                                                                                                                                                                                                                                                                           |
| `influencers:write` | Create, clone, and patch influencers.                                                                                                                                                                                                                                                                |
| `leased:read`       | List and read leased-account requests.                                                                                                                                                                                                                                                               |
| `leased:write`      | Submit and release leased-account requests.                                                                                                                                                                                                                                                          |
| `engagement:read`   | Read auto-pilot engagement config.                                                                                                                                                                                                                                                                   |
| `engagement:write`  | Patch auto-pilot engagement config.                                                                                                                                                                                                                                                                  |
| `credits:read`      | Read the org credit balance, ledger, and per-format estimated costs.                                                                                                                                                                                                                                 |
| `github:admin`      | Register and read GitHub App installations.                                                                                                                                                                                                                                                          |
| `jobs:read`         | Read async [job](/docs/api/concepts/jobs) status.                                                                                                                                                                                                                                                    |
| `jobs:cancel`       | Cancel running async jobs.                                                                                                                                                                                                                                                                           |
| `webhooks:write`    | Register, rotate, replay, and delete webhook endpoints (read-side shares this scope today).                                                                                                                                                                                                          |
| `org:admin`         | The sub-organization control plane: create / list / get / patch / suspend / resume / archive [child organizations](/docs/api/concepts/organizations), mint child keys, allocate credits, and act on a child via the `X-Layers-Organization` header. **Deny-by-default + non-delegable** — see below. |

<Callout type="warn">
  `org:admin` is **deny-by-default and non-delegable**. It governs minting customer orgs, draining wallets, and offboarding, so a key must carry it **explicitly** — no wildcard confers it (not even `*`), partner/internal-tier keys do not bypass it, and it can never be granted to a [child key](#child-keys-for-sub-organizations). It is minted only through the organization-management key flow, never selected alongside ordinary data scopes.
</Callout>

### Parent and child orgs [#parent-and-child-orgs]

When you run customers as child organizations, your top-level (parent) key carries `org:admin`. To operate *inside* a child — create a project for that customer, read its credits — send the [`X-Layers-Organization`](/docs/api/getting-started/authentication#acting-on-behalf-of-a-child-org) header with the parent key. A child that isn't a direct child of your org resolves to `404`.

When you'd rather give a customer its own credential instead of driving it from the parent, mint a [child-scoped key](#child-keys-for-sub-organizations) (below): a least-privilege key bound to one child org, with zero-downtime rotation.

### Pending scope additions (this release) [#pending-scope-additions-this-release]

The following scopes are **planned** for the API integration redesign and are documented on the surfaces that depend on them; they are not yet in the canonical `PARTNER_API_SCOPES` enum at the moment this page lands. Until they ship, the listed routes use the closest existing scope (typically `projects:write` for resource-create routes, `ads:write` for ads CAPI). Track [the changelog](/docs/api/operational/changelog) for the exact ship date.

| Scope                    | Will gate                                                                        | Status                                                                 |
| ------------------------ | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `sdk:read` / `sdk:write` | SDK app CRUD + [`verify-tracking`](/docs/api/reference/sdk-apps/verify-tracking) | Pending — covered by `projects:write` / `projects:read` until shipped. |

### Ads sub-scopes [#ads-sub-scopes]

The `ads:*` family is fine-grained. Mint only the sub-scopes the partner integration actually uses. Each corresponds to a specific endpoint class on the [ads write surface](/docs/api/concepts/ads-write-model).

| Scope                         | Gates                                                                                                                                                                                                                        |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ads:read`                    | Reading campaigns, ad sets, ads, metrics, audit log, pending queue.                                                                                                                                                          |
| `ads:write:campaigns`         | Create / patch / delete campaigns. The simplified Create-campaigns endpoint requires this.                                                                                                                                   |
| `ads:write:budgets`           | `PATCH …/campaigns/:id/budget`, `…/adsets/:id/budget`, daily/lifetime cap mutations.                                                                                                                                         |
| `ads:write:creative`          | Push / replace / prune ad creatives.                                                                                                                                                                                         |
| `ads:write:lifecycle`         | Pause / resume / archive across the campaign / adset / ad hierarchy.                                                                                                                                                         |
| `ads:write:policy`            | Authority CRUD (`PATCH …/campaigns/:id/authority`, `…/adgroups/:id/authority`). Customer's own bucket-mode controls remain authoritative — this scope governs partner-issued authority changes inside the customer's policy. |
| `ads:write:optimizer_trigger` | `POST …/optimizer/run` and the per-layer write-class flags.                                                                                                                                                                  |
| `ads:write:pending`           | Approve / reject pending optimizer actions.                                                                                                                                                                                  |
| `ads:write:capi`              | Patch a project's CAPI config (Meta + TikTok). Wires through to `project_layers.config.capi`.                                                                                                                                |

The legacy single `ads:write` scope is mapped to the union of the sub-scopes above for backwards compatibility on existing keys; new keys should mint sub-scopes explicitly.

There is no `ads:write:kill_switch` scope. Kill-switch is customer-only; partner keys cannot trip it. Partners observe the kill-switch state passively via the [audit log](/docs/api/reference/audit-log/list) and the `ads.write.denied` webhook.

### Wildcard scopes [#wildcard-scopes]

The scope check accepts three wildcard forms — useful when minting an internal-tier key that should pass any future scope your endpoints adopt without re-issuing the key:

| Wildcard                | Covers                                                                                                                                            |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `*`                     | Every scope &#x2A;*except the non-delegable `org:admin`**, which always requires an exact grant. Reserved for internal/admin keys.                |
| `<resource>:*`          | Every action on a resource. Example: `ads:*` covers `ads:read`, `ads:write`, every `ads:write:<sub>`, etc.                                        |
| `<resource>:<action>:*` | Every sub-scope under a resource:action pair. Example: `ads:write:*` covers all eight `ads:write:*` sub-scopes but does **not** cover `ads:read`. |

Wildcards expand at request time via `hasScope()` (`apps/api/src/lib/partner/scope.ts`); they don't pre-mint sub-scopes onto the key. `/v1/whoami` returns the wildcard string verbatim — partners reading scope strings programmatically should evaluate wildcards rather than expecting expansion.

**Empty scope lists are denied.** A key with an empty/missing `scopes` array now grants **nothing** (deny-by-default) — it is not a "full access" passthrough. Keys that pre-date this change were migrated to the explicit `*` (god-mode) sentinel so they keep their prior access; every new key must be minted with at least one scope. Even the `*` god-mode wildcard does **not** confer the non-delegable [`org:admin`](#scopes) — that scope requires an exact grant.

## Creating API keys [#creating-api-keys]

Every key carries an explicit set of [scopes](#scopes) chosen when the key is created. Because enforcement is deny-by-default, **you must select at least one scope** — a scope-less key can't call anything.

There are two ways to create a key:

* **Your own organization's key** — from the Layers dashboard under **Org Settings → API Keys**. The creation dialog has an expandable **Permissions** section: pick the scopes this key needs, then create. The full key secret is shown **once** on creation — copy it immediately.
* **A per-customer child key** — mint it programmatically against a child org with your parent (`org:admin`) key via [`POST /v1/organizations/:orgId/api-keys`](/docs/api/reference/organizations/api-keys/mint). The request **requires** a non-empty `scopes` array (a subset of your own key's scopes); see [child keys](#child-keys-for-sub-organizations).

### Choosing scopes [#choosing-scopes]

Grant the **least privilege** the integration needs — read scopes for read-only consumers, the matching `:write` scope only where the key actually mutates state. The [scope table above](#scopes) lists every scope and what it gates. `org:admin` is never selectable here: it is minted only through the organization-management key flow and is never delegable to a child.

### Scope is one-way [#scope-is-one-way]

**A key's scopes are fixed at creation and cannot be edited afterward.** There is no "change permissions" operation — narrowing or widening a key's access means minting a **new** key with the desired scopes and revoking (or [rotating](#rotation) out) the old one. This keeps a key's authority auditable and immutable for its whole lifetime; key lists display the granted scopes read-only.

## Rate-limit tiers [#rate-limit-tiers]

Tier is a property of the key, not the endpoint. Buckets are keyed per `(key_id, endpoint_class)` so a runaway generation loop can't starve your reads. Endpoint classes: `read-light`, `write-light`, `long-running`.

| Tier       | Typical provisioning                                                             |
| ---------- | -------------------------------------------------------------------------------- |
| `standard` | Default for every partner key.                                                   |
| `pilot`    | Higher throughput for early-integration partners - granted by Layers on request. |
| `partner`  | Enterprise tier for GA partners with SLAs.                                       |

Every `429` response carries:

* `Retry-After`
* `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`
* `X-RateLimit-Endpoint-Class`, `X-RateLimit-Tier`
* Body with `error.details.retryAfterMs` and `error.details.endpointClass`

See [common patterns](/docs/api/getting-started/common-patterns#rate-limit-signals) for a back-off snippet and [rate limits](/docs/api/operational/rate-limits) for the full bucket policy.

## Child keys for sub-organizations [#child-keys-for-sub-organizations]

If you manage customers as [sub-organizations](/docs/api/concepts/organizations), you can mint API keys directly for each child org with your parent (`org:admin`) key. A child key is a per-customer credential: it's bound to exactly one child org and carries exactly the access you grant it.

Two rules govern what a child key may hold:

* **Scope subsetting.** A child key can only carry scopes you already hold. Request a scope your parent key doesn't have and the mint is rejected with `403 FORBIDDEN_SCOPE` (`details.offendingScopes` names the offenders). You can never grant a child more than you have.
* **`org:admin` is never delegable.** Even if your parent key holds `org:admin` (or a `*` wildcard), a child key can never receive it. The child must not run the control plane - minting its own children, moving projects, or draining wallets. Requesting `org:admin` for a child returns `403 FORBIDDEN_SCOPE`.

The full lifecycle is parent-driven and lives under the child org's path:

* [Mint](/docs/api/reference/organizations/api-keys/mint) - `POST /v1/organizations/:orgId/api-keys`. Secret returned once.
* [List](/docs/api/reference/organizations/api-keys/list) - masked; never the secret.
* [Rotate](/docs/api/reference/organizations/api-keys/rotate) - zero-downtime, see below.
* [Delete](/docs/api/reference/organizations/api-keys/delete) - immediate revoke.

These routes are anti-enumeration: a `:orgId` that isn't your direct child, or a `:keyId` that isn't that child's, both return `404 NOT_FOUND` - a stranger's resource looks identical to a missing one. A malformed id returns `422 VALIDATION`.

## Rotation [#rotation]

You can rotate your own (parent / top-level) key without downtime:

<Steps>
  <Step>
    Ask Layers to create a second key with the same access.
  </Step>

  <Step>
    Deploy the new secret to your callers.
  </Step>

  <Step>
    Confirm callers have stopped using the old key.
  </Step>

  <Step>
    Revoke the old key.
  </Step>
</Steps>

Both keys are active in parallel during the cutover, so there's no flap.

### Rotation grace window [#rotation-grace-window]

[Child keys](#child-keys-for-sub-organizations) rotate through a built-in API instead of a manual two-key dance. [`POST …/api-keys/:keyId/rotate`](/docs/api/reference/organizations/api-keys/rotate) mints a fresh key (new id, new secret, same scopes) and puts the **old secret into a 24-hour grace window** so there's no downtime while you roll out the new one:

<Steps>
  <Step>
    **Rotate.**

     You get the new key + its one-time secret back in the response.
  </Step>

  <Step>
    **Deploy the new secret**

     across the customer's fleet within 24 hours. The old secret keeps authenticating the whole time, so nothing flaps.
  </Step>

  <Step>
    **The old secret stops automatically**

     at the deadline. List the child's keys to see it - the old row carries 

    `graceUntil`

     (when it dies) and 

    `supersededBy`

     (the new key's id). Once 

    `graceUntil`

     passes, the old secret returns 

    `401 UNAUTHENTICATED`

    .
  </Step>
</Steps>

To cut the old secret off before the window closes, [delete](/docs/api/reference/organizations/api-keys/delete) the old key - that's an immediate, grace-free revoke. A key rotates **once**: the rotation chain rolls forward (K → K2 → K3), and rotating an already-superseded key returns `409 CONFLICT` - always rotate the current key.

<Callout type="warn">
  The grace window never overrides a kill switch. A child key - new or old, in grace or not - is rejected with `503 KILL_SWITCH` the moment its org is suspended or archived, or a per-key kill switch is flipped. Grace only postpones an old secret's normal expiry; it never resurrects access that ops has cut.
</Callout>

## Revocation [#revocation]

Three levers, each stronger than the last:

* **Revoke the key.** Every subsequent request fails with `401 UNAUTHENTICATED`. This is the normal path.
* **Kill switch on the key.** Requests fail with `503 KILL_SWITCH` - a different signal than revoked, so you can tell an incident from a rotation. Clearable without reissuing.
* **Org-wide kill switch.** Every key on the org fails. Reserved for incident response.

<Callout>
  Revocation and kill-switch flips take effect on the next request - propagation is effectively immediate.
</Callout>
