Authentication
Every /v1 route takes one of two credentials, and which one you hold decides
what you may call.
| Credential | Who sends it | Looks like |
|---|---|---|
| API key | n8n, your backend, CI | Authorization: Bearer pm_live_… |
| Session JWT | The editor and the dashboard | Authorization: Bearer eyJhbGci… |
Both resolve to the same account. Neither is interchangeable with the other for every route — see Scopes.
Sending an API key
The canonical header is Authorization:
Authorization: Bearer pm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX-API-Key is accepted as an alias, carrying the bare key with no Bearer
prefix. It exists because it is the default header of n8n’s generic
credential type:
X-API-Key: pm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxSend one or the other, not both. Missing either answers 401.
Key format
| Property | Value |
|---|---|
| Prefix | pm_live_ |
| Body | 24 random bytes, base64url |
| Stored as | HMAC-SHA256 under a server-side pepper — never the secret itself |
| Displayed after creation | The first 14 characters only (pm_live_ab12cd) |
Because only the hash is stored, a lost secret is unrecoverable. Revoke and mint a new one.
Scopes
A key carries a list of scopes. The route you call determines the scope
required, and a key without it gets 403 insufficient_scope.
| Scope | Granted by default | Lets a key |
|---|---|---|
renders:read | ✓ | List, poll and download renders |
renders:write | ✓ | Create and cancel render jobs, and upload assets |
templates:read | ✓ | List and read templates |
templates:write | — | Import a project, publish and delete templates |
usage:read | ✓ | Read /v1/usage |
Creating a key with no scopes field grants the four defaults.
templates:write is the one you have to ask for, and importing a .motion
needs it. A key without it gets 403 insufficient_scope from
POST /v1/templates/import. If your workflow only renders from templates that
already exist, leave it off.
Why uploads ride on renders:write
POST /v1/assets does not have a scope of its own. A new assets:write would
be the tidier taxonomy and the worse migration: scopes are stored per key, so it
would be absent from every key already issued, and the first thing anyone tried
after upgrading would be a 403 on a route that had just been announced.
The privilege is honest, too — a temporary asset exists only to feed a render, is unreadable without its unguessable URL, and expires in 24 hours. A key that can create renders can already spend the quota that matters.
Key management is session-only. /v1/keys — list, create, revoke —
rejects pm_live_ keys with 403 session_required, whatever their
scopes. A leaked key therefore cannot mint more keys, revoke the ones you
have, or hide its own tracks.
Deny-by-default
Scope mapping is a deny-list, not an allow-list. A /v1 route added without an
explicit scope mapping is treated as session-only until somebody decides which
scope covers it. New endpoints are never silently reachable by every key that
already exists.
Expiry
expiresAt is an optional ISO timestamp, and it must be in the future.
The editor’s key dialog offers never, 30 days, 90 days and 1 year. An expired
key answers 401 exactly like an invalid one.
{ "name": "CI — nightly reels", "expiresAt": "2027-08-27T00:00:00.000Z" }Revocation
DELETE /v1/keys/{id}
Authorization: Bearer <session JWT>Revocation takes effect on the very next request — the guard reads revokedAt
on every call, so there is no cache to wait out. Revoking twice is not an
error.
What is tracked
Each key records lastUsedAt and a requestCount, both visible in the key
list. If you are wondering whether an old key is still wired into something,
that is where to look before you revoke it.
Rotating a key
Plans allow more than one active key precisely so you can rotate without
downtime: mint the new key, deploy it, confirm lastUsedAt on the old key
stops moving, then revoke the old one. Doing it in the other order takes your
workflow down for the length of the deploy.
Errors
| Status | Code | Cause |
|---|---|---|
| 401 | — | No credential, unknown key, revoked key, expired key, bad JWT |
| 403 | session_required | An API key called a session-only route |
| 403 | insufficient_scope | The key lacks the scope this route requires |
| 403 | upgrade_required | The plan does not include API access |
| 403 | key_limit_reached | Already at the plan’s active-key cap |
The insufficient_scope body names what was missing, so you can fix it without
guessing:
{
"code": "insufficient_scope",
"required": "renders:write",
"message": "This API key does not have the renders:write scope."
}See Errors for the full envelope.