Skip to main content

Authentication

Every call to the Admin API (/orgs/{orgId}/api/v1/admin/**) must carry a credential. This page tells you which one to use, what it can reach, and how to send it.

Which credential for which job​

You are buildingUseSend it asIt can reach
A server, cron job, CI pipeline or provisioning scriptAPI key (lmk_…)Authorization: ApiKey lmk_… or X-API-Key: lmk_…The Admin API resources named by the key's scopes, plus dynamic client registration (admin:clients:register) and the authorization decision endpoints (authz:check)
An app that acts for a signed-in administratorOAuth Bearer tokenAuthorization: Bearer eyJ… (or DPoP eyJ… for sender-constrained tokens)The Admin API when the token carries an admin scope and the user holds the endpoint's permission; every other organization API according to its user scopes
The LumoAuth portal itself, or a first-party browser app on the same hostSession cookieSet by the portal login; browser sends it automaticallyThe portal, account pages and the Admin API, authorised by the user's permissions (plus a CSRF witness header on writes)
A registered AI agentAgent API key (agk_…)Authorization: Bearer agk_…Agent, JIT and authorization-check routes only. See Agent API keys

Two rules apply to API keys and Bearer tokens alike:

  • They never create a browser session. The response never sets a session cookie, and the credential must be presented on every call.
  • They are refused on session-only pages. A Bearer token or API key sent to /orgs/{orgId}/portal/** or /account/** gets 403 forbidden (bearer_not_accepted for tokens, api_key_not_accepted for keys). Those surfaces authorise on the live browser session and its MFA state; no token can stand in for that.
Standard

The bearer token standard (OAuth 2.0 bearer token usage) defines how an access token travels in the Authorization: Bearer header and how a resource server answers when it is missing or invalid (401 with a WWW-Authenticate: Bearer challenge, 403 for insufficient scope). To comply, send the token only in that header over HTTPS, never in a query string, and read the WWW-Authenticate header on 401 to learn why the token was rejected. Read the standard.

Do this: three copy-paste examples​

1. API key (server to server)​

Create a key under Applications & developer tools → API keys in the portal (/orgs/{orgId}/portal/settings/api-keys), tick the scopes it needs (here admin:users:read), and copy the value once.

The Permissions section of the Create API key form with admin:users ticked

List users with an API key
curl https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/users \
-H "Authorization: ApiKey lmk_abc123xyz..."

X-API-Key: lmk_… is accepted as an alternative header. Do not send an lmk_ key as Authorization: Bearer; that header is reserved for OAuth tokens and the key will be rejected with 401.

2. OAuth Bearer token (app acting for an administrator)​

  1. In the portal, open the OAuth client your app uses and add the admin scope it needs to the client's allowed scopes (for example admin:users:read, or the blanket admin).
  2. Request that scope in the authorization request: scope=openid profile admin:users:read.
  3. Call the Admin API with the resulting access token. The signed-in user must hold the permission the endpoint requires (here users.view; see Required permissions).
List users with an access token
curl https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/users \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9..."

See OAuth 2.0 Token Endpoint for how to obtain tokens and Scopes for the admin scope family.

Standard

The JWT access token profile says an access token issued as a JWT must carry the JOSE header typ: at+jwt, so that ID tokens, logout tokens and other JWTs signed by the same key can never be mistaken for access tokens. LumoAuth mints access tokens with typ: at+jwt and its resource servers (the Admin API and SCIM) refuse any other typ. If you validate LumoAuth access tokens yourself, check typ before the signature. Read the standard.

A page served from the LumoAuth host (the portal, or your own first-party UI on the same origin) already has the session cookie. Read requests need nothing extra. Writes must add a CSRF witness header and come from the same origin:

Create a user from the portal origin
const resp = await fetch("/orgs/acme-corp/api/v1/admin/users", {
method: "POST",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest"
},
body: JSON.stringify({ email: "jane@acme.com", name: "Jane Smith" })
});

See CSRF protection for session-authenticated requests.

How authorization works​

API keys​

An API key is an organization-bound credential whose reach is exactly its granted scopes. Every Admin API action declares the scope it requires, and the check runs before the action, per resource, and fails closed:

  1. The key must belong to the organization in the URL, otherwise 401.
  2. Read requests (GET, HEAD, OPTIONS) need admin:<resource>:read for that resource. Scopes are matched exactly: a :write scope does not stand in for :read, so a job that both lists and changes users needs admin:users:read and admin:users:write.
  3. Mutating requests (POST, PUT, PATCH, DELETE) need admin:<resource>:write. A read scope never authorises a write.
  4. A scope for one resource never unlocks another: admin:webhooks:read gets 403 on /admin/users.
  5. admin:clients:register is a registration-only grant. It unlocks POST /orgs/{orgId}/api/v1/connect/register and nothing else on the Admin API.
  6. The person who created the key must still be active (not blocked, deactivated or deleted) and must still hold the permission the endpoint requires. If they lose it, every key they created gets 403 on that endpoint.

There is no blanket admin scope for API keys. Grant the resource scopes you need from the scope catalogue.

Admin API scopes for Bearer tokens​

An access token is accepted on the Admin API only when both hold:

  1. the token's subject holds the endpoint's permission in the organization, and
  2. the token carries an admin scope: either the blanket admin scope, or a resource scope admin:<resource>:<read|write> from the same catalogue as API keys. The read/write class rule above applies (a write needs a :write scope), and per-resource isolation applies too.

The blanket admin scope satisfies every resource check but never exceeds the user's own permissions. Tokens issued for ordinary applications (openid profile email) get 403 forbidden on the Admin API even when the user is an administrator. Dynamically registered clients cannot obtain privileged scopes such as admin; add them to a client created in the portal.

Tokens must be OAuth access tokens: JWTs with typ: at+jwt, or opaque tokens from the token endpoint. ID tokens, logout tokens, and JIT or agent tokens are refused on the Admin API.

Session cookies​

A portal session authorises through the user's permissions and roles, not through scopes. Bearer and API-key scope checks do not apply. Writes require the CSRF witness described below.

Required permissions​

Scopes say what a credential may reach. Permissions say what the person behind it may do. Every Admin API action declares the permission it needs, and the check runs before the action for every caller:

CallerWho must hold the permission
Browser sessionThe signed-in user
OAuth Bearer tokenThe token's user (the token also needs the scope, see above)
API key (lmk_)The key's creator, re-checked on every request, on top of the key's own scope

Permissions are any-of: where a row lists two, either one is enough. An action that declares no permission is refused for everyone (the check fails closed). A failure returns 403 forbidden and names what was missing:

403 forbidden (missing permission)
{
"error": "forbidden",
"message": "Access denied: missing required permission",
"status": 403,
"details": {
"required_permissions": ["users.delete"]
}
}

For an API key the message is Access denied: the API key owner does not hold the required permission. A key can therefore never do more than the administrator who created it can do today: demote that person and their keys lose the same reach.

settings.manage is not "full admin"

settings.manage governs authentication and security policy (sign-in, MFA and session policy, PATCH /admin/tenant and /admin/organization, audit-log retention, sandboxes) plus, in the portal, API keys, custom domains, directory (LDAP) and HR connections, log shippers and the other integrations. It does not unlock users, roles, clients, webhooks or any other resource: those need their own permission from the tables below. Grant the resource permissions a job needs; see Roles & Permissions.

Account-takeover containment​

Some user actions hand the caller control of another account: setting a password, changing the e-mail, activating or deactivating, removing an authenticator, issuing a temporary access code, blocking, deactivating and deleting. They succeed only when the caller already holds every permission the target user holds in the organization. Otherwise the answer is 403 forbidden with You cannot perform this action on an account that holds privileges you do not have. (or, for authenticators, You cannot change the sign-in methods of an account that holds privileges you do not have.). For API keys the key's creator is the caller. A password set by an administrator, including the initial password on POST /admin/users, must also satisfy the organization's password policy and breached-password check (400 otherwise).

Permission per endpoint​

Paths are relative to /orgs/{orgId}/api/v1.

Users​

MethodEndpointPermissionAPI-key / token scope
GET/admin/usersusers.viewadmin:users:read
GET/admin/users/{userId}users.viewadmin:users:read
POST/admin/usersusers.createadmin:users:write
PUT PATCH/admin/users/{userId}users.editadmin:users:write
DELETE/admin/users/{userId}users.deleteadmin:users:write
POST/admin/users/{userId}/blockusers.editadmin:users:write
POST/admin/users/{userId}/unblockusers.editadmin:users:write
POST/admin/users/{userId}/mfa/resetusers.editadmin:users:write
GET/admin/users/{userId}/rolesusers.viewadmin:users:read
PUT/admin/users/{userId}/rolesusers.editadmin:users:write
POST/admin/users/{userId}/rolesusers.editadmin:users:write
DELETE/admin/users/{userId}/roles/{roleId}users.editadmin:users:write
GET/admin/users/{userId}/groupsusers.viewadmin:users:read
PUT/admin/users/{userId}/groupsusers.editadmin:users:write
POST/admin/users/{userId}/groupsusers.editadmin:users:write
DELETE/admin/users/{userId}/groups/{groupId}users.editadmin:users:write
GET/admin/users/{userId}/permissionsusers.viewadmin:users:read
POST/admin/users/{userId}/permissionsusers.editadmin:users:write
DELETE/admin/users/{userId}/permissions/{permissionId}users.editadmin:users:write
POST/admin/users/{userId}/verify-emailusers.editadmin:users:write
POST/admin/users/{userId}/mark-verifiedusers.editadmin:users:write
POST/admin/users/{userId}/password-resetusers.editadmin:users:write
PUT POST/admin/users/{userId}/passwordusers.editadmin:users:write

MFA (admin)​

MethodEndpointPermissionAPI-key / token scope
GET/admin/users/{userId}/authenticatorsusers.viewadmin:users:read
DELETE/admin/users/{userId}/authenticators/{authenticatorId}users.editadmin:users:write
POST/admin/users/{userId}/temporary-access-codeusers.editadmin:users:write
GET/admin/policies/mfasettings.viewadmin:settings:read
PUT/admin/policies/mfasettings.manageadmin:settings:write
GET/admin/reports/mfa-coverageusers.viewadmin:users:read

Sessions and tokens​

MethodEndpointPermissionAPI-key / token scope
GET/admin/sessionsusers.viewadmin:sessions:read
GET/admin/users/{userId}/sessionsusers.viewadmin:sessions:read
DELETE/admin/sessions/{sessionId}users.editadmin:sessions:write
DELETE/admin/users/{userId}/sessionsusers.editadmin:sessions:write
GET/admin/tokensusers.viewadmin:sessions:read
DELETE/admin/tokens/{tokenId}users.editadmin:sessions:write
DELETE/admin/users/{userId}/tokensusers.editadmin:sessions:write
POST/admin/users/{userId}/sessions/revokeusers.editadmin:sessions:write
POST/admin/sessions/revoke-allusers.editadmin:sessions:write
POST/admin/users/{userId}/tokens/revokeusers.editadmin:sessions:write
POST/admin/clients/{clientId}/tokens/revokeclients.editadmin:sessions:write
DELETE/admin/clients/{clientId}/tokensclients.editadmin:sessions:write
GET/admin/sessions/statsusers.viewadmin:sessions:read
GET/admin/sessions/countusers.viewadmin:sessions:read

Roles​

MethodEndpointPermissionAPI-key / token scope
GET/admin/rolesroles.viewadmin:roles:read
GET/admin/roles/{roleId}roles.viewadmin:roles:read
POST/admin/rolesroles.createadmin:roles:write
PUT PATCH/admin/roles/{roleId}roles.editadmin:roles:write
DELETE/admin/roles/{roleId}roles.deleteadmin:roles:write
GET/admin/roles/{roleId}/permissionsroles.viewadmin:roles:read
PUT/admin/roles/{roleId}/permissionsroles.assign-permissionsadmin:roles:write
POST/admin/roles/{roleId}/permissionsroles.assign-permissionsadmin:roles:write
DELETE/admin/roles/{roleId}/permissions/{permissionId}roles.assign-permissionsadmin:roles:write
GET/admin/roles/{roleId}/usersroles.viewadmin:roles:read
POST/admin/roles/{roleId}/usersroles.editadmin:roles:write
DELETE/admin/roles/{roleId}/users/{userId}roles.editadmin:roles:write

Groups​

MethodEndpointPermissionAPI-key / token scope
GET/admin/groupsgroups.viewadmin:groups:read
GET/admin/groups/{groupId}groups.viewadmin:groups:read
POST/admin/groupsgroups.createadmin:groups:write
PUT PATCH/admin/groups/{groupId}groups.editadmin:groups:write
DELETE/admin/groups/{groupId}groups.deleteadmin:groups:write
GET/admin/groups/{groupId}/membersgroups.viewadmin:groups:read
POST/admin/groups/{groupId}/membersgroups.editadmin:groups:write
DELETE/admin/groups/{groupId}/members/{userId}groups.editadmin:groups:write
GET/admin/groups/{groupId}/rolesgroups.viewadmin:groups:read
POST/admin/groups/{groupId}/rolesgroups.editadmin:groups:write
DELETE/admin/groups/{groupId}/roles/{roleId}groups.editadmin:groups:write
PUT/admin/groups/{groupId}/rolesgroups.editadmin:groups:write

Permissions and OAuth scopes​

MethodEndpointPermissionAPI-key / token scope
GET/admin/permissionsroles.viewadmin:permissions:read
GET/admin/permissions/{permissionId}roles.viewadmin:permissions:read
POST/admin/permissionsroles.assign-permissionsadmin:permissions:write
PATCH/admin/permissions/{permissionId}roles.assign-permissionsadmin:permissions:write
DELETE/admin/permissions/{permissionId}roles.assign-permissionsadmin:permissions:write
GET/admin/permissions/{permissionId}/usageroles.viewadmin:permissions:read
GET/admin/scopesroles.viewadmin:permissions:read
POST/admin/scopesroles.assign-permissionsadmin:permissions:write
DELETE/admin/scopes/{scopeId}roles.assign-permissionsadmin:permissions:write

OAuth clients​

MethodEndpointPermissionAPI-key / token scope
GET/admin/clientsclients.viewadmin:clients:read
GET/admin/clients/{clientId}clients.viewadmin:clients:read
POST/admin/clientsclients.createadmin:clients:write
PUT PATCH/admin/clients/{clientId}clients.editadmin:clients:write
DELETE/admin/clients/{clientId}clients.deleteadmin:clients:write
POST/admin/clients/{clientId}/enableclients.editadmin:clients:write
POST/admin/clients/{clientId}/disableclients.editadmin:clients:write
GET/admin/clients/{clientId}/scopesclients.viewadmin:clients:read
PUT/admin/clients/{clientId}/scopesclients.editadmin:clients:write
POST/admin/clients/{clientId}/rotate-secretclients.rotate-secretadmin:clients:write

Customer organizations​

MethodEndpointPermissionAPI-key / token scope
GET/admin/organizationsorganizations.viewadmin:users:read
POST/admin/organizationsorganizations.createadmin:users:write
GET/admin/organizations/{organizationId}organizations.viewadmin:users:read
PUT PATCH/admin/organizations/{organizationId}organizations.editadmin:users:write
DELETE/admin/organizations/{organizationId}organizations.deleteadmin:users:write
GET/admin/organizations/{organizationId}/membersorganizations.viewadmin:users:read
POST/admin/organizations/{organizationId}/membersorganizations.members.manageadmin:users:write
PUT PATCH/admin/organizations/{organizationId}/members/{userId}organizations.members.manageadmin:users:write
DELETE/admin/organizations/{organizationId}/members/{userId}organizations.members.manageadmin:users:write
GET/admin/organizations/{organizationId}/rolesorganizations.viewadmin:roles:read
POST/admin/organizations/{organizationId}/rolesorganizations.editadmin:roles:write
PUT PATCH/admin/organizations/{organizationId}/roles/{roleId}organizations.editadmin:roles:write
DELETE/admin/organizations/{organizationId}/roles/{roleId}organizations.editadmin:roles:write
GET/admin/organizations/{organizationId}/invitationsorganizations.viewadmin:users:read
POST/admin/organizations/{organizationId}/invitationsorganizations.invitations.manageadmin:users:write
DELETE/admin/organizations/{organizationId}/invitations/{invId}organizations.invitations.manageadmin:users:write
POST/admin/organizations/{organizationId}/invitations/{invId}/resendorganizations.invitations.manageadmin:users:write

Social providers​

MethodEndpointPermissionAPI-key / token scope
GET/admin/social-providersidps.viewadmin:social:read
GET/admin/social-providers/availableidps.viewadmin:social:read
GET/admin/social-providers/typesidps.viewadmin:social:read
GET/admin/social-providers/callback-urlsidps.viewadmin:social:read
GET/admin/social-providers/{providerId}idps.viewadmin:social:read
POST/admin/social-providersidps.createadmin:social:write
PUT PATCH/admin/social-providers/{providerId}idps.editadmin:social:write
DELETE/admin/social-providers/{providerId}idps.deleteadmin:social:write
POST/admin/social-providers/{providerId}/enableidps.editadmin:social:write
POST/admin/social-providers/{providerId}/disableidps.editadmin:social:write

Webhooks​

MethodEndpointPermissionAPI-key / token scope
GET/admin/webhookswebhooks.viewadmin:webhooks:read
GET/admin/webhooks/eventswebhooks.viewadmin:webhooks:read
GET/admin/webhooks/{webhookId}webhooks.viewadmin:webhooks:read
POST/admin/webhookswebhooks.createadmin:webhooks:write
PUT PATCH/admin/webhooks/{webhookId}webhooks.editadmin:webhooks:write
POST/admin/webhooks/{webhookId}/enablewebhooks.editadmin:webhooks:write
POST/admin/webhooks/{webhookId}/disablewebhooks.editadmin:webhooks:write
DELETE/admin/webhooks/{webhookId}webhooks.deleteadmin:webhooks:write
POST/admin/webhooks/{webhookId}/testwebhooks.editadmin:webhooks:write
POST/admin/webhooks/{webhookId}/rotate-secretwebhooks.editadmin:webhooks:write
GET/admin/webhooks/{webhookId}/deliverieswebhooks.viewadmin:webhooks:read
GET/admin/webhooks/{webhookId}/deliveries/{deliveryId}webhooks.viewadmin:webhooks:read
POST/admin/webhooks/{webhookId}/deliveries/{deliveryId}/replaywebhooks.editadmin:webhooks:write
POST/admin/webhooks/tunnel/startwebhooks.editadmin:webhooks:write
POST/admin/webhooks/tunnel/{webhookId}/stopwebhooks.editadmin:webhooks:write
GET/admin/webhooks/tunnel/{webhookId}/streamwebhooks.viewadmin:webhooks:read

Shared Signals streams​

MethodEndpointPermissionAPI-key / token scope
POST/ssf/streamwebhooks.createadmin:webhooks:write
GET/ssf/streamwebhooks.viewadmin:webhooks:read
DELETE/ssf/streamwebhooks.deleteadmin:webhooks:write
POST/ssf/verifywebhooks.editadmin:webhooks:write

Audit logs​

MethodEndpointPermissionAPI-key / token scope
GET/admin/audit-logsaudit.viewadmin:audit:read
GET/admin/audit-logs/{logId}audit.viewadmin:audit:read
GET/admin/audit-logs/statsaudit.viewadmin:audit:read
GET/admin/audit-logs/actionsaudit.viewadmin:audit:read
GET/admin/audit-logs/retentionaudit.viewadmin:audit:read
PUT PATCH/admin/audit-logs/retentionsettings.manageadmin:settings:write
GET/admin/audit-logs/exportaudit.exportadmin:audit:read

Settings, analytics and organization​

MethodEndpointPermissionAPI-key / token scope
GET/admin/settingssettings.viewadmin:settings:read
GET/admin/settings/generalsettings.viewadmin:settings:read
PATCH PUT/admin/settings/generalsettings.editadmin:settings:write
GET/admin/settings/authenticationsettings.viewadmin:settings:read
PATCH PUT/admin/settings/authenticationsettings.manageadmin:settings:write
GET/admin/settings/securitysettings.viewadmin:settings:read
PATCH PUT/admin/settings/securitysettings.manageadmin:settings:write
GET/admin/settings/emailsettings.viewadmin:settings:read
PATCH PUT/admin/settings/emailsettings.editadmin:settings:write
GET/admin/tenantsettings.viewadmin:settings:read
GET/admin/organizationsettings.viewadmin:settings:read
PUT PATCH/admin/tenantsettings.manageadmin:settings:write
PUT PATCH/admin/organizationsettings.manageadmin:settings:write
GET/admin/settings/authsettings.viewadmin:settings:read
PUT PATCH/admin/settings/authsettings.manageadmin:settings:write
GET/admin/settings/brandingbranding.viewadmin:settings:read
PUT PATCH/admin/settings/brandingbranding.editadmin:settings:write
GET/admin/settings/scimscim.viewadmin:settings:read
PUT PATCH/admin/settings/scimscim.editadmin:settings:write
GET/admin/analytics/dashboardanalytics.viewadmin:analytics:read
GET/admin/analytics/loginsanalytics.viewadmin:analytics:read
GET/admin/analytics/usersanalytics.viewadmin:analytics:read

Email templates​

MethodEndpointPermissionAPI-key / token scope
GET/admin/email-templatessettings.viewadmin:settings:read
GET/admin/email-templates/{type}settings.viewadmin:settings:read
PUT/admin/email-templates/{type}settings.editadmin:settings:write
DELETE/admin/email-templates/{type}settings.editadmin:settings:write
POST/admin/email-templates/{type}/previewsettings.editadmin:settings:write
GET/admin/email-templates/{type}/variablessettings.viewadmin:settings:read

Sandboxes​

MethodEndpointPermissionAPI-key / token scope
POST/admin/sandbox/spawnsettings.manageadmin:settings:write
GET/admin/sandboxsettings.viewadmin:settings:read
POST/admin/sandbox/{sandboxSlug}/destroysettings.manageadmin:settings:write

AI agents​

MethodEndpointPermissionAPI-key / token scope
GET/admin/agentsagents.viewadmin:agents:read
GET/admin/agents/{agentId}agents.viewadmin:agents:read
POST/admin/agentsagents.createadmin:agents:write
PUT PATCH/admin/agents/{agentId}agents.editadmin:agents:write
POST/admin/agents/{agentId}/rotate-keyagents.editadmin:agents:write
POST/admin/agents/{agentId}/enableagents.editadmin:agents:write
POST/admin/agents/{agentId}/disableagents.editadmin:agents:write
DELETE/admin/agents/{agentId}agents.deleteadmin:agents:write
POST/admin/agents/{agentId}/revoke-keyagents.editadmin:agents:write
GET/admin/agents/{agentId}/scopesagents.viewadmin:agents:read
PUT/admin/agents/{agentId}/scopesagents.editadmin:agents:write
POST/admin/agents/{agentId}/rotate-credentialsagents.editadmin:agents:write
POST/admin/agents/{agentId}/activateagents.editadmin:agents:write
POST/admin/agents/{agentId}/deactivateagents.editadmin:agents:write
POST/admin/agents/{agentId}/tokenagents.editadmin:agents:write

MCP servers​

MethodEndpointPermissionAPI-key / token scope
GET/admin/mcp/serversagents.viewadmin:agents:read
POST/admin/mcp/serversagents.createadmin:agents:write
GET/admin/mcp/servers/{serverId}agents.viewadmin:agents:read
DELETE/admin/mcp/servers/{serverId}agents.deleteadmin:agents:write

ABAC attributes​

MethodEndpointPermissionAPI-key / token scope
GET/abac/attributesabac.viewadmin:abac:read
GET/abac/attributes/{id}abac.viewadmin:abac:read
POST/abac/attributesabac.manageadmin:abac:write
PUT PATCH/abac/attributes/{id}abac.manageadmin:abac:write
DELETE/abac/attributes/{id}abac.manageadmin:abac:write

ABAC policies​

MethodEndpointPermissionAPI-key / token scope
GET/abac/policiesabac.viewadmin:abac:read
GET/abac/policies/{id}abac.viewadmin:abac:read
POST/abac/policiesabac.manageadmin:abac:write
PUT PATCH/abac/policies/{id}abac.manageadmin:abac:write
DELETE/abac/policies/{id}abac.manageadmin:abac:write
POST/abac/policies/{id}/toggleabac.manageadmin:abac:write

Scope catalogue​

These are the scopes you can grant to an API key. OAuth clients can additionally be allowed the blanket admin scope.

ScopeUnlocks
admin:users:readList and view users
admin:users:writeCreate, update, delete, block and unblock users
admin:roles:readList and view roles
admin:roles:writeCreate, update, delete roles and assign permissions
admin:groups:readList and view groups
admin:groups:writeCreate, update, delete groups and manage members
admin:clients:readList and view OAuth clients
admin:clients:writeCreate, update, delete OAuth clients and rotate secrets
admin:clients:registerDynamic client registration only
admin:webhooks:readList and view webhooks
admin:webhooks:writeCreate, update, delete webhooks and manage deliveries
admin:audit:readList, view and export audit logs
admin:social:readList and view social login providers
admin:social:writeCreate, update, delete social login providers
admin:agents:readList and view AI agents
admin:agents:writeCreate, update, delete agents and manage credentials
admin:permissions:readList and view permissions and OAuth scopes
admin:permissions:writeCreate, update, delete permissions and OAuth scopes
admin:settings:readRead organization and authentication settings
admin:settings:writeUpdate organization, authentication, branding and AI settings
admin:analytics:readView dashboard and analytics data
admin:sessions:readList active sessions and tokens
admin:sessions:writeRevoke sessions and tokens
admin:abac:readList and view ABAC attributes and policies
admin:abac:writeCreate, update, delete ABAC attributes and policies
authz:checkAsk authorization decisions about subjects other than the key owner (check, AuthZEN, Zanzibar check and expand)

Which scope guards which endpoint​

Some endpoint families have no scope of their own and use the closest one.

Endpoint familyResource scope
/admin/users/** (incl. roles, groups, permissions, passwords)admin:users:*
/admin/users/{userId}/sessions, /admin/users/{userId}/tokensadmin:sessions:*
/admin/organizations/** (customer organizations, members, invitations)admin:users:*
/admin/organizations/{organizationId}/roles/**admin:roles:*
/admin/roles/**admin:roles:*
/admin/groups/**admin:groups:*
/admin/clients/**admin:clients:*
/admin/clients/{clientId}/tokens, /admin/clients/{clientId}/tokens/revokeadmin:sessions:write
/admin/webhooks/** and /orgs/{orgId}/api/v1/ssf/** (Shared Signals streams)admin:webhooks:*
/admin/audit-logs/**admin:audit:read (retention update: admin:settings:write)
/admin/social-providers/**admin:social:*
/admin/agents/** and /admin/mcp/servers/**admin:agents:*
/admin/permissions/** and /admin/scopes/**admin:permissions:*
/admin/settings/**, /admin/organization, /admin/email-templates/**, /admin/sandbox/**admin:settings:*
/admin/analytics/**admin:analytics:read
/admin/sessions/**, /admin/tokens/**admin:sessions:*
/orgs/{orgId}/api/v1/abac/attributes/**, /orgs/{orgId}/api/v1/abac/policies/**admin:abac:*
/orgs/{orgId}/api/v1/connect/registeradmin:clients:register
/api/v1/authz/**, /orgs/{orgId}/api/v1/authz/**, /orgs/{orgId}/api/v1/zanzibar/**authz:check

API Keys​

API keys are long-lived credentials bound to one organization. They start with lmk_ (example: lmk_s3cr3t_abc123) and are shown in full only once, at creation.

Generating API Keys​

  1. Navigate to Applications & developer tools → API keys in your organization portal.
  2. Click Create API key.
  3. Give the key a descriptive name (for example production-sync) and select the scopes it needs.
  4. Choose an Expiration (or No expiration).
  5. Click Create API key and copy the generated key. It is displayed only once.

The API keys page with the Create API key button

The Create API key form with the name, description and expiration

warning

Store API keys in a secret manager (AWS Secrets Manager, Vault, and so on). Never commit them to source control, and never ship them in client-side code.

The user guide covers rotation, revocation and best practices: API keys.

Agent API Keys​

An agent API key (agk_ prefix) is the first-class credential minted when an agent is registered. It is distinct from the admin lmk_ API key: lmk_ keys authorize human or service admin access to the Admin API, while an agk_ key authenticates the agent itself as a ROLE_AGENT principal.

Present it as a Bearer token, the same Authorization header agents use for their OAuth access tokens:

Agent API key
curl https://app.lumoauth.dev/orgs/acme-corp/api/v1/agents/me \
-H "Authorization: Bearer agk_abc123xyz..."

Unlike the admin key, an agk_ key is accepted only on agent-scoped routes and fails closed everywhere else:

  • /orgs/{orgId}/api/v1/agents/*: agent identity, Ask API, per-tool MCP authorization
  • /orgs/{orgId}/api/v1/jit/*: JIT permission tasks and token requests
  • /api/v1/authz/check*: the (org-agnostic) authorization decision point

The key resolves to the registered agent and is subject to the same conditional access policies as an agent OAuth token. See the Agent Registry for how to obtain and rotate it.

Bearer Tokens​

Bearer tokens are short-lived JWTs or opaque tokens issued by the OAuth 2.0 token endpoint. Use them when acting on behalf of a user, or for machine-to-machine flows via the Client Credentials grant.

Bearer token on an OAuth-protected endpoint
curl https://app.lumoauth.dev/orgs/acme-corp/api/v1/oauth/userinfo \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9..."

For the Admin API, the token must additionally carry an admin scope; see Admin API scopes for Bearer tokens.

Sender-constrained (DPoP) tokens​

If the token was issued with DPoP, send it as Authorization: DPoP <token> together with a fresh DPoP proof header signed by the same key. A DPoP-bound token presented as a plain Bearer token, or with a proof from a different key, is rejected with 401.

Standard

Sender-constrained tokens (DPoP) bind an access token to a client-held key so that a stolen token is useless without that key. To comply, generate a key pair per client instance, send a DPoP proof JWT (typ: dpop+jwt, fresh jti, htm, htu, iat, and ath when a token is presented) on every request, and honour use_dpop_nonce challenges by retrying with the server-issued DPoP-Nonce. LumoAuth makes nonces mandatory per organization through settings.security.dpop_require_nonce. Read the standard and DPoP in LumoAuth.

CSRF Protection for Session-Authenticated Requests​

State-changing Admin API requests (POST, PUT, PATCH, DELETE) authenticated with a session cookie must pass two checks:

  1. Include an X-Requested-With or X-CSRF-Token header (any non-empty value). Browsers never attach custom headers to a cross-site form post, so their presence proves a script on your origin made the call.
  2. If the browser sends an Origin header (or, failing that, a Referer), it must name the LumoAuth host serving the request. A foreign or null origin is refused. Requests that send neither header are accepted.

Either failure returns 403:

{
"error": "forbidden",
"message": "CSRF protection: state-changing admin requests must include an X-Requested-With or X-CSRF-Token header.",
"status": 403
}
Session-authenticated write request
curl -X POST https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/users \
-H "X-Requested-With: XMLHttpRequest" \
-H "Origin: https://app.lumoauth.dev" \
-H "Content-Type: application/json" \
--cookie "session=..." \
-d '{ "email": "jane@acme.com", "name": "Jane Smith" }'
note

API-key and Bearer-token callers are exempt. They are not cookie-bound, so classic CSRF does not apply and no extra header is required. The server-to-server examples throughout this reference need no CSRF header.

CORS and the Admin API​

The origins registered on your OAuth clients (allowed_origins) are honoured for the public OAuth, OIDC and user-facing APIs, never for the Admin API. A browser app on a different origin cannot call /orgs/{orgId}/api/v1/admin/** with the user's session cookie. Only the LumoAuth host itself and the platform's own origin allow-list are accepted there. Call the Admin API from your backend with an API key instead.

Authentication Errors​

StatuserrorMeaning
401invalid_api_keyThe API key is missing, malformed, expired, revoked, belongs to another organization, or its creator is no longer active. WWW-Authenticate: ApiKey realm="Admin API".
401invalid_tokenThe Bearer token is missing, expired, has the wrong typ, fails signature or issuer checks, or is DPoP-bound and presented without a valid proof. WWW-Authenticate: Bearer ….
401unauthorizedNo credential at all on an endpoint that requires one.
403forbiddenThe credential is valid but not allowed here: missing scope, wrong scope class (read scope on a write), Bearer token without an admin scope, user or key creator without the endpoint's permission, a token or key on a session-only page, or a failed CSRF witness. details.required_scopes and details.granted_scopes are present for scope failures, details.required_permissions for permission failures.
429rate_limitedThe credential spent its per-credential budget (600 requests per 60 seconds by default). Wait for Retry-After seconds. See Rate limits.
403access_policy_deniedAn agent credential was blocked by a conditional access policy; reason carries the policy code.

The full envelope is described in Errors.

Common mistakes​

  • 403 forbidden on the Admin API with an OAuth token that works elsewhere. The token lacks an admin scope. Add admin or admin:<resource>:<read|write> to the client's allowed scopes and request it.
  • 403 on a POST with a key that can GET the same resource. The key holds only a :read scope. Writes need :write.
  • 403 on /admin/users with a key made for webhooks. Scopes are per resource. Grant admin:users:read as well, or create a separate key.
  • 403 and the key used to work. The person who created the key was deactivated, or no longer holds the permission in details.required_permissions. Create a new key from an active administrator who holds it.
  • 403 with details.required_permissions although the user holds settings.manage. settings.manage is not a blanket admin grant. Give the user (or the key's creator) the listed resource permission, for example users.view.
  • 401 invalid_token when sending Authorization: Bearer lmk_…. API keys go in Authorization: ApiKey … or X-API-Key.
  • 401 invalid_token with a valid-looking JWT. It is an ID token, not an access token. Only typ: at+jwt JWTs (or opaque access tokens) are accepted.
  • Portal pages return 403 bearer_not_accepted. The portal and account pages are session-only. Sign in through the browser; use the Admin API for automation.
  • 403 "CSRF protection" from a browser app. Add X-Requested-With (or X-CSRF-Token) to the request and make sure it is served from the LumoAuth host. Cross-origin browser calls to the Admin API are not possible; move the call to your backend.
  • No cookie after authenticating with a key or token. That is by design. Credentials are stateless and must be sent on every request.

Standards used on this page​

Plain nameStandard
Bearer tokensRFC 6750
JWT access token profileRFC 9068
Dynamic client registrationRFC 7591 and RFC 7592
Sender-constrained tokens (DPoP)RFC 9449

See the standards reference for what each one covers and how LumoAuth uses it.