Skip to content

API keys

Included free on every install, up to five active keys per tenant and the last thirty days of each key's request history. A license with the apikey-pro feature lifts both and lets you set or raise a key's monthly request limit. See pricing.

An API key lets a program call the Content API (/api/v1/*) without a person signing in. Each key carries scopes that say what it may read and write, an optional expiry and an optional monthly request limit. Every request a key makes is recorded against it, refused ones included.

CheckWhat passes
The keySent in X-API-Key, enabled and not expired. Anything else answers 401.
The scopeThe scope the method and path need is one the key holds.
The roleWrites to content need the editor, admin or super_admin role on the key.
The monthly limitThe key has requests left this calendar month (UTC).

A scope is <resource>:<action>. The resource is the first path segment after /api/v1/, so /api/v1/content/post is content. The action comes from the method:

MethodAction
GET, HEAD, OPTIONSread
POST, PUT, PATCHwrite
DELETEdelete

content:read reads content. content:* allows every action on content, *:read reads everything, and *:* allows everything. Scopes ignore case. A key with no scopes is refused everywhere.

A key with the admin or super_admin role skips the scope check and reaches every Content API route the role reaches. It must expire within 90 days, and the Admin API refuses it.

You need an admin token in TOKEN. The quickstart shows how to get one, and creates the post content type used here.

  1. Create a key that may only read content:

    Terminal window
    curl -X POST http://localhost:3001/api/admin/api-keys \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"name": "website-build", "scopes": ["content:read"]}'
    {
    "id": "8c2e4f1a-3b6d-4a9e-b0c7-1d5f2e8a6b94",
    "name": "website-build",
    "roles": [],
    "schemas": [],
    "scopes": ["content:read"],
    "tenant_id": "default",
    "enabled": true,
    "monthly_limit": 0,
    "created_at": "2026-10-01T09:30:00Z",
    "raw_key": "ly_3f9a..."
    }

    The answer is 201. Copy raw_key into KEY and id into KEY_ID. raw_key is shown once. Only a hash is kept, so a lost key cannot be recovered.

  2. Read content with the key:

    Terminal window
    curl http://localhost:3002/api/v1/content/post \
    -H "X-API-Key: $KEY"

    The answer is the list of posts.

  3. Try to write with it:

    Terminal window
    curl -X POST http://localhost:3002/api/v1/content/post \
    -H "X-API-Key: $KEY" \
    -H "Content-Type: application/json" \
    -d '{"data": {"title": "From a key"}}'
    { "error": "insufficient scope" }
  4. See what the key did:

    Terminal window
    curl "http://localhost:3001/api/admin/api-keys/$KEY_ID/audit?limit=10" \
    -H "Authorization: Bearer $TOKEN"
    [
    { "id": "f3bc4e46-...", "api_key_id": "8c2e4f1a-...", "method": "POST", "path": "/api/v1/content/post", "status": 403, "ip": "203.0.113.7", "ts": "2026-10-01T09:31:02Z" },
    { "id": "fcf64173-...", "api_key_id": "8c2e4f1a-...", "method": "GET", "path": "/api/v1/content/post", "status": 200, "ip": "203.0.113.7", "ts": "2026-10-01T09:31:00Z" }
    ]
  5. See this month's usage:

    Terminal window
    curl http://localhost:3001/api/admin/usage/api-key/$KEY_ID \
    -H "Authorization: Bearer $TOKEN"

    The answer holds billing_period, requests, bytes_in and bytes_out.

  6. Revoke the key:

    Terminal window
    curl -X POST http://localhost:3001/api/admin/api-keys/$KEY_ID/revoke \
    -H "Authorization: Bearer $TOKEN"

    The answer is 204. From then on the key answers 401 with authentication required.

A tenant holds five active keys without apikey-pro. A revoked key does not count. If a license with apikey-pro ends while a tenant holds more than five, every key keeps working, and the tenant creates no new one until it is under five again. The request history is not shortened when a license ends: the console reads the last thirty days, and the rest returns with apikey-pro.

A key can carry a monthly request limit, and past it the key answers 429. Setting a first limit or raising one needs a license with apikey-pro, while lowering or clearing one is free. See API key request limits.

Give it the editor role and the write scope. Only roles you hold can be granted, unless you are a super admin:

{ "name": "catalog-import", "roles": ["editor"], "scopes": ["content:read", "content:write"] }

Without the role, a write answers 403 with insufficient permissions. Without the scope, it answers 403 with insufficient scope.

Keys are managed from a signed-in session by an admin or super_admin. In the admin console, a super admin opens Access > API keys. A request made with an API key or an admin token cannot create, revoke or delete keys.

Revoking turns a key off and keeps its history. Deleting removes the key and its history. To replace a leaked key, create a new one, move the program to it, then revoke the old one.

Key routes
MethodPathResult
GET/api/admin/api-keysYour tenant's keys, paginated with limit and offset. The secret is never shown.
POST/api/admin/api-keysCreates a key. 201. Without apikey-pro, a tenant that already holds five active keys gets 402 with "error": "cap_exceeded" and "cap": "apikey.keys". Revoke a key to free its place.
POST/api/admin/api-keys/{id}/revokeTurns the key off. 204.
DELETE/api/admin/api-keys/{id}Deletes the key and its history. 204.
GET/api/admin/api-keys/{id}/auditThe key's requests, newest first, with limit (default 50) and offset. Without apikey-pro, the last thirty days.
PATCH/api/admin/api-keys/{id}/monthly-limitSets {"monthly_limit": 50000}. 0 removes the limit. See API key request limits.
GET/api/admin/usage/api-key/{id}Requests and bytes this month.
Fields of a new key
FieldDefaultMeaning
namerequiredA label for the key.
scopes[]What the key may do.
roles[]Roles the key acts with.
schemas[]Stored and shown, not enforced.
expires_atnoneWhen the key stops working. Required within 90 days for an admin or super_admin key.
monthly_limit0Requests per calendar month in UTC. 0 means no limit. Any other value needs apikey-pro. See API key request limits.
VariableWhat it doesDefault
APIKEY_RETENTION_DAYSDays a history entry, with its client address, is kept. 0 or less keeps it forever. A value that is not a whole number uses the default.365
API_KEY_PEPPERA server secret mixed into every stored key hash, so a copy of the database alone cannot check or forge keys.unset
ENFORCE_API_KEY_PEPPERtrue stops the instance from starting unless API_KEY_PEPPER is set.false

API keys run on every install. If you set LYEVE_PLUGINS, include apikey, and usage, which counts each key's requests and enforces its monthly limit. See licensing and tiers.

StatusMessageCause
401authentication requiredThe key is unknown, revoked or expired.
403insufficient scopeThe key's scopes do not cover the route.
429monthly request limit (100000) exceededThe key used its monthly limit. The answer carries X-RateLimit-Exceeded: true.
Errors from the key routes
StatusMessageCause
400invalid JSON, name is requiredThe body is malformed or has no name.
402payment_requiredSetting a first monthly limit or raising one without apikey-pro.
401API keys with an admin role are not accepted on the admin API. Use an admin token.An admin-role key called the Admin API.
403insufficient permissionsThe key lacks the role a write needs.
403API keys are managed by a signed-in user, not by another keyA key called a key management route.
403This route needs a signed-in session.An admin token called a key management route.
403cannot grant roles you do not possessThe new key asks for a role you lack.
404api key not foundNo key with that id in your tenant.
422a key with the admin or super_admin role needs an expiry, at most 90 days awayAn admin-role key has no expires_at.
422a key with the admin or super_admin role expires at most 90 days awayAn admin-role key expires too late.