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:

TransportHeaderNotes
Bearer token (canonical)Authorization: Bearer kbn_live_...Preferred.
API key headerX-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, and alias (the alias it was created from — read, write, or admin — or null when 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:

ResourceScopes
Applicationsapplications:read, applications:add, applications:edit, applications:delete, applications:comment
Infrastructure (servers, networks, routers, workstations)infrastructure:read, infrastructure:add, infrastructure:edit, infrastructure:delete
Agentsagents:read
Data objectsdata:read, data:add, data:edit, data:delete
Contractscontracts:read, contracts:add, contracts:edit, contracts:delete
Taxonomycategories:read/add/edit/delete, tags:read/add/delete
Organization & teamsorganisation:read, organisation:add, organisation:edit, organisation:delete
Peopleusers:read, members:read, members:edit, members:delete
Announcementsannounces:read, announces:add, announces:edit, announces:delete
Audit logaudit_log:read
Workspacetenant:read, tenant:edit
Dashboards & insightsfinance_dashboard:read, usage_dashboard:read, operations_dashboard:read, architecture_dashboard:read, technology_dashboard:read, workstations_dashboard:read
Diagramsflow_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 aliasread, write, or admin — which snapshots the permission set of the matching system role at creation time. The key's alias field (seen in GET /me) records which alias it came from, but the granted permissions are 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:action codes. In this case alias is null.

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: Bearer and no X-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, not admin.
  • 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.