Authentication and API keys
How Kabeen Public API keys work: headers, permission scopes, key lifecycle, and failure modes
The Kabeen Public API authenticates every request with an API key — a workspace-scoped bearer credential. There is no OAuth flow, no session cookie, and no login step: a workspace admin creates a key once from the Kabeen application, and you present it on every request.
How it works
- An API key belongs to exactly one workspace. The workspace is bound to the key at creation time and is never passed in the request path, query string, or body — every endpoint operates implicitly on "the workspace this key belongs to."
- A key carries a fixed set of permission scopes (e.g.
applications:read,infrastructure:edit). Every endpoint requires exactly one scope, documented in the Endpoint reference. - Keys are created, rotated, and revoked by a workspace admin from inside the Kabeen application — the public API itself exposes no endpoint to manage keys.
- All public API traffic is authenticated as a service actor (the key), never as a human member. Audit log entries for public API calls record the key as the actor.
Presenting the key
Every generated key starts with the kbn_ prefix (in practice kbn_live_...). The API accepts the key over two equivalent transports — use whichever fits your HTTP client better:
| Transport | Header | Notes |
|---|---|---|
| Bearer token (canonical) | Authorization: Bearer kbn_live_... | Preferred. |
| API key header | X-Api-Key: kbn_live_... | Equivalent alternative, useful for clients without first-class Authorization: Bearer support. |
Both are recognized by the same authentication mechanism, only on paths under /public/. If both headers are present, X-Api-Key takes precedence.
curl https://app.kabeen.io/public/v1/me \
-H "Authorization: Bearer kbn_live_51gv9c2b7f2a4e6f8c1a0d3b6e9f2c5a8"curl https://app.kabeen.io/public/v1/me \
-H "X-Api-Key: kbn_live_51gv9c2b7f2a4e6f8c1a0d3b6e9f2c5a8"Introspection: GET /me
Before wiring up anything else, call GET /public/v1/me. It requires only a valid key (no specific scope) and tells you exactly which workspace you're bound to and what the key can do — useful both as a "does my key work" smoke test and for building integrations that adapt to whatever scopes they were granted.
{
"workspace": {
"id": "3f9c9f2e-8a71-4e2a-9b0d-2c5f6a1d7e44",
"name": "Acme Corp"
},
"key": {
"id": "8b2e6a4d-1c3f-4a9e-9d7b-5f0e2c8a6b31",
"name": "Finance integration",
"alias": "read"
},
"permissions": [
"applications:read",
"contracts:read",
"data:read",
"infrastructure:read"
]
}workspace— the workspace this key is bound to (id + display name).key— the key's own metadata:id,name, andalias(the alias it was created from —read,write, oradmin— ornullwhen the key was created from a custom, explicit set of scopes).permissions— the exact, sorted list of permission scopes this key grants. This is the authoritative source of truth for what the key can call; treat it as the contract, not the alias name.
Permission scopes
Every endpoint requires exactly one permission scope, shaped resource:action:
| Resource | Scopes |
|---|---|
| Applications | applications:read, applications:add, applications:edit, applications:delete, applications:comment |
| Infrastructure (servers, networks, routers, workstations) | infrastructure:read, infrastructure:add, infrastructure:edit, infrastructure:delete |
| Agents | agents:read |
| Data objects | data:read, data:add, data:edit, data:delete |
| Contracts | contracts:read, contracts:add, contracts:edit, contracts:delete |
| Taxonomy | categories:read/add/edit/delete, tags:read/add/delete |
| Organization & teams | organisation:read, organisation:add, organisation:edit, organisation:delete |
| People | users:read, members:read, members:edit, members:delete |
| Announcements | announces:read, announces:add, announces:edit, announces:delete |
| Audit log | audit_log:read |
| Workspace | tenant:read, tenant:edit |
| Dashboards & insights | finance_dashboard:read, usage_dashboard:read, operations_dashboard:read, architecture_dashboard:read, technology_dashboard:read, workstations_dashboard:read |
| Diagrams | flow_mapping_diagram:read, application_matrix_diagram:read, application_quadrant_diagram:read, application_lifecycle_diagram:read, capacity_map_diagram:read, network_mapping_diagram:read |
Aliases vs explicit scopes
A key is scoped in exactly one of two ways, chosen when it is created:
- An alias —
read,write, oradmin— which snapshots the permission set of the matching system role at creation time. The key'saliasfield (seen inGET /me) records which alias it came from, but the grantedpermissionsare the frozen snapshot, not a live link to the role: if the role's permissions change later, existing keys keep what they were granted. - An explicit list of
resource:actioncodes. In this casealiasisnull.
In both cases, a key can never be granted a scope its creator does not personally hold at creation time — an admin cannot mint a key with more power than their own account has.
When a scope is missing
Calling an endpoint without its required scope returns 403 Forbidden:
{
"code": "insufficient_permission",
"message": "missing permission: applications:edit",
"status": 403
}Don't hardcode assumptions about what a key can do — call GET /me and check permissions before attempting a call, or handle 403/insufficient_permission gracefully and surface it to whoever manages the integration's key.
Key lifecycle and management
API keys are created, rotated, and revoked by a workspace admin from inside the Kabeen application — key management is not part of the public API itself. What to expect operationally:
- Creation — an admin names the key and scopes it via either an alias (
read/write/admin) or an explicit list of permission codes (never both, never neither), with an optional expiration date. The plaintext secret is returned exactly once, at creation time — Kabeen does not store it in recoverable form and cannot show it to you again. Only a SHA-256 hash of the secret is persisted; what you can retrieve afterwards is metadata plus a short display prefix of the key. - Rotation — rotating a key generates a brand new secret (again shown once) and invalidates the old secret immediately, while keeping the same key id, name, and scopes.
- Revocation — a revoked or deleted key stops working immediately: every request looks up the key by its secret's hash and requires it to currently be active, so there is no caching window or delayed propagation.
- Expiration — a key created with an expiration date stops authenticating once that instant passes, enforced by the same per-request, no-cache lookup.
Failure modes
Authentication failures return 401 Unauthorized in all of the following cases:
- No credential presented at all (no
Authorization: Bearerand noX-Api-Key). - The credential doesn't start with the
kbn_prefix (malformed / not a Kabeen key). - The key doesn't match any currently active key (wrong, revoked, or deleted).
- The key has passed its expiration date.
A real 401 is rejected at the authentication layer, before the API's uniform error body can be attached: the response has an empty body and a WWW-Authenticate: Bearer realm="kbine", error="invalid_token" header. Don't try to parse JSON out of a 401 — see Pagination and errors.
403 Forbidden is a distinct failure mode: the key is valid and authenticated, but lacks the scope the endpoint requires.
Security best practices
- Treat the key as a secret. Anyone holding it can act as a service identity in your workspace, scoped to whatever permissions it was granted.
- Never commit keys to source control or embed them in client-side code (browser JS, mobile apps, public repos). The public API is designed for server-to-server integrations.
- Store keys in environment variables or a secret manager, not in config files checked into a repo.
- Prefer least-privilege scopes. A read-only reporting integration should get a
read(or a custom read-only) key, notadmin. - Rotate periodically, and immediately after any suspected exposure — rotation invalidates the old secret the moment the new one is issued.
- Revoke keys you no longer use. Revocation takes effect on the very next request, so there's no operational cost to revoking promptly.
- Set an expiration for temporary or time-boxed integrations instead of relying on manual revocation later.