API keys
One credential story, three uses
Edit this pageOn this page
One credential story, three uses. Daemon keys, SCIM tokens and integration
keys are all rows in the same api_keys table with the same hashing and the
same revocation path — because a second credential mechanism is a second
place to get it wrong.
What a key looks like
lsk_a3f8... a user or integration key — the REST API, the CLI, MCP
lsd_9c21... a daemon key — enrolment, heartbeat, observationsThis page said both used lsk_. They do not, and the difference is load
bearing: requireApiKey matches lsk_ and nothing else, so presenting a
daemon key to the public API is a 401 rather than a partial success — and
Settings → API keys deliberately does not list or revoke lsd_ rows,
because revoking a scanner's key from a screen that says nothing about
scanners would stop discovery with no obvious cause. Daemon keys are issued
and rotated in Settings → Daemons.
The prefix is otherwise there so a key found in a log, a support ticket or a screenshot is identifiable as this product's — and findable by an automated secret scanner — without anyone guessing where it came from.
Issuing one
Settings → API keys. Give it a name that says what uses it, tick the scopes it needs, and optionally an expiry.
Nothing is ticked by default, deliberately: a key that arrives holding
everything is a key nobody narrows afterwards. And a key can never be given
more than the person issuing it holds — asking for scan:run as a viewer is
refused at creation, and the floor is re-checked on every request, so
lowering somebody's role disarms their keys at that moment rather than at
expiry.
| Scope | What it grants | Needs |
|---|---|---|
inventory:read | Read hosts, services, findings, certificates | viewer |
metrics:read | Scrape the Prometheus endpoint | viewer |
inventory:write | Register a network, change a finding's state, take a snapshot | editor |
scan:run | Queue a discovery scan — separate because it puts packets on the network | editor |
scim:manage | Provision and deprovision users over SCIM | admin |
Issuing and revoking both write an audit-log entry naming the key and its scopes.
Shown once
Only the SHA-256 hash is stored. The moment after creation is the only time the value exists in the clear — a lost key is revoked and replaced, not recovered.
That applies equally to invitation links and to the daemon key. It is the honest behaviour, and the one an identity team expects.
Scopes
| Scope | Grants | Cannot |
|---|---|---|
| daemon | Register, heartbeat, poll jobs, post observations | Provision members |
scim:manage | The full SCIM 2.0 surface | Post observations |
A daemon key cannot provision members and a SCIM token cannot post observations. The scope is the only thing separating them, and it is checked on every request.
Creating
| Kind | Where |
|---|---|
| Daemon key | Settings → Daemons |
| SCIM token | Settings → SCIM |
Both require admin or owner.
Revoking
Delete the key. The next request using it gets a 401 — there is no grace period and no cached authorisation, because a revocation that takes effect in five minutes is not a revocation you can rely on during an incident.
Revoke when:
- A daemon host is decommissioned or rebuilt.
- Somebody who held the key leaves.
- A key appears anywhere it should not — a shared document, a log, a chat.
Last used
Every key records last_used_at, updated best-effort on each request. It is
the fastest way to answer "is this key still in use, or can I delete it" —
and a key that has not been used in months is a key worth removing.
The update is best-effort by design: a failure to record the timestamp must not fail a request that was otherwise authorised.
Never logged
A redacting logger drops API keys, credentials, SNMP community strings and session tokens before they reach a log line. Structured logging makes that enforceable — the fields are named, so they can be filtered, which is not true of interpolated strings.
CSRF
Cookie-authenticated state-changing endpoints require a CSRF token. API keys are CSRF-exempt, because CSRF is an attack on ambient credentials the browser attaches automatically, and a bearer token is not one.
What there is not yet
- No public REST API for inventory.
/api/v1/today carries the daemon protocol and a few administrative endpoints. A general read API is on the roadmap; see REST API. - No key expiry by policy. Keys support an expiry timestamp, but nothing enforces rotation.
- No per-key IP allowlist.