API keys (personal access tokens)
An API key is a native, revocable personal access token (PAT) issued and verified entirely by Lucenia. It is the practical way for a person or an automated agent to authenticate any request to the cluster without embedding a password or minting a fresh token for every call. A key is a pair — an id and a high-entropy secret — bound to an identity, carrying a bounded set of roles, and expiring on a schedule. The secret is never stored in plaintext (only a bcrypt hash is kept) and a key can be revoked at any time. Any part of the product that authorizes requests works with a key-authenticated caller exactly as it does with any other user; the memory verbs are one such consumer (see Using API keys with memory tenancy).
How API keys compare with the other token-based backends:
- HTTP basic authentication verifies a username and password against the internal user database on every request. An API key stores no recoverable secret — only a bcrypt hash — and carries its own expiration and revocation.
- JSON Web Tokens are self-contained tokens minted and signed by an external identity provider; Lucenia only verifies the signature and cannot revoke an individual token. An API key needs no external IdP — Lucenia issues it — and it is individually revocable at any time.
API keys authenticate to the whole cluster, not to any single feature. They are the token most teams hand to an agent (for example, a coding assistant calling Lucenia over MCP) so it acts as a specific user with a narrow, revocable set of roles.
Table of contents
Enable API keys
Two independent switches govern API keys, and both must be on for keys to authenticate requests:
- The management feature — creating, listing, and revoking keys — is controlled by the node setting
plugins.security.apikey.enabled, which defaults totrue. When it isfalse, every API-key REST call returns400 "The API key feature is disabled on this node." - The authenticator — actually logging in with a key — is off until you enable the
apikey_auth_domainin the securityconfig.yml. The feature being enabled lets you manage keys; the auth domain being enabled lets a presented key authenticate a request.
The reference config.yml ships the domain disabled:
apikey_auth_domain:
description: "Authenticate via native, revocable API keys (personal access tokens)"
http_enabled: false # set to true to accept API-key logins
transport_enabled: false
order: 1
http_authenticator:
type: apikey
challenge: false
authentication_backend:
type: noop
Set http_enabled: true and load the security configuration. Because the token already carries everything needed to identify the caller, the domain is non-challenging (challenge: false) and uses a noop authentication backend.
Ordering requirement. A non-challenging authenticator only runs if it is evaluated before a challenging one. Keep apikey_auth_domain at a lower order than the challenging basic_internal_auth_domain (the reference configuration ships the API-key domain at order: 1 and the basic domain at order: 4). If the API-key domain is placed after basic, basic issues its 401 first and the API-key authenticator never gets a chance to run.
Settings
All settings are node-scoped.
| Setting | Default | Purpose |
|---|---|---|
plugins.security.apikey.enabled | true | Master switch for the create/list/get/revoke management API and the authenticator. |
plugins.security.apikey.default_expiration | 30d | Expiration applied when a create request omits one. |
plugins.security.apikey.max_expiration | 365d | Hard cap; a requested expiration is clamped to this. |
plugins.security.apikey.cache_refresh_interval | 10s | How often each node reloads the key set from the index (bounds cross-node revocation lag). |
plugins.security.apikey.index_name | .opendistro_security_api_keys | Protected system index backing the stored keys. |
Create (mint) a key
PUT /_plugins/_security/api/apikey. The body needs a name; everything else is optional.
PUT /_plugins/_security/api/apikey
{
"name": "ci-deploy-bot",
"security_roles": ["readall"],
"backend_roles": ["automation"],
"expiration": "30d"
}
| Field | Type | Notes |
|---|---|---|
name | string | Required. A label for the key. |
security_roles | array | Internal security roles to grant the key. Also accepted under the alias roles. |
backend_roles | array | Backend roles the key carries (subject to role mapping). |
expiration | string | A duration such as "30d". Defaults to default_expiration, capped at max_expiration. |
The response returns the token once — the secret is shown here and never again:
{
"id": "k-8f2a...",
"name": "ci-deploy-bot",
"api_key": "azLThjJhLi4uLi4uOnNlY3JldA==",
"expiration_at": 1754092800000
}
api_key is the ready-to-use token: a base64 encoding of id:secret. Capture it now — the secret is stored only as a bcrypt hash and cannot be retrieved later. If you lose it, revoke the key and mint a new one.
Use a key
Send the token in the Authorization header with the ApiKey scheme:
Authorization: ApiKey azLThjJhLi4uLi4uOnNlY3JldA==
The value is the api_key string from the create response — i.e. base64(id:secret). The scheme keyword is case-insensitive. Every request so authenticated runs as the key's identity, with the key's roles, and is subject to the same authorization checks as any other user. As with basic auth and JWTs, always transmit keys over HTTPS.
POST /my-index/_search
Authorization: ApiKey azLThjJhLi4uLi4uOnNlY3JldA==
Content-Type: application/json
{ "query": { "match_all": {} } }
List and get keys
GET /_plugins/_security/api/apikey returns every key's metadata (never the secret or its hash):
{
"api_keys": [
{
"id": "k-8f2a...",
"name": "ci-deploy-bot",
"backend_roles": ["automation"],
"security_roles": ["readall"],
"creator": "release-manager",
"created_at": 1751500800000,
"expiration_at": 1754092800000,
"invalidated": false
}
]
}
GET /_plugins/_security/api/apikey/{id} returns a single key's metadata (404 if unknown). The stored secret hash is never included in either response.
Revoke a key
DELETE /_plugins/_security/api/apikey/{id} revokes by id:
{ "id": "k-8f2a...", "invalidated": true }
Revocation is a soft invalidate — the key is marked invalidated (its document is retained, not deleted) and it stops authenticating. A 404 is returned for an unknown id.
Cross-node revocation lag. Each node authenticates keys from an in-memory cache refreshed on plugins.security.apikey.cache_refresh_interval (default 10s). Revocation is immediate on the node that handles the DELETE, but other nodes converge within one refresh interval — so a revoked key may remain valid on other nodes for up to that interval. Lower cache_refresh_interval to tighten the window (at the cost of more frequent reloads).
Limit-by-owner
A key can never grant more authority than its creator holds:
- When an authenticated user creates a key, every requested
security_rolesandbackend_rolesentry must be a role the creator already holds. Requesting a role the creator lacks returns403. - If the create request supplies no roles, the key inherits the creator's own security and backend roles.
So a key is always a subset of its creator's authority and can never escalate privileges. Grant a key exactly the roles it needs and no more. (A request authenticated by a trusted cluster admin certificate is not subject to this restriction, since it is already gated by the route permission below.)
Security properties
- High-entropy secret. The secret is 32 bytes (256 bits) drawn from a
SecureRandom. - Hashed at rest. Only a bcrypt hash of the secret is persisted (produced by the same hasher used for internal-user passwords); the plaintext secret is never stored and is returned exactly once at creation.
- Constant-time verification. A presented secret is checked against the stored hash in constant time — a bcrypt verification runs even for an unknown key id — so verification timing does not leak whether an id exists.
- Protected index. Keys live in a protected system index (default
.opendistro_security_api_keys) that only the security module writes to (internally, with superadmin privileges). It cannot be read or written directly through the normal REST/index APIs, so key material cannot be exfiltrated or forged by writing to the index.
Permissions
Each management route is gated by its own cluster permission, so you can delegate key administration narrowly:
| Operation | Endpoint | Permission |
|---|---|---|
| Create | PUT /_plugins/_security/api/apikey | security:apikey/create |
| List | GET /_plugins/_security/api/apikey | security:apikey/list |
| Get one | GET /_plugins/_security/api/apikey/{id} | security:apikey/get |
| Revoke | DELETE /_plugins/_security/api/apikey/{id} | security:apikey/invalidate |
Related pages
- HTTP basic authentication — the challenging username/password backend an API-key domain is ordered ahead of.
- JSON Web Token — externally issued, non-revocable tokens, contrasted above.
- Using API keys with memory tenancy — memory-specific guidance for keys, including sourcing an organization from a backend role.