Authentication
Kapable supports two customer authentication methods: API keys for server-to-server calls and session/JWT bearer tokens for user-context requests. Operator surfaces use a separate token tier and a separate SDK — see Operator SDK.
Token Types
| Token | Tier | Header | Use |
|---|---|---|---|
sk_live_* / sk_test_* / sk_org_* | Customer | x-api-key | Server-to-server, org-bound, scoped (read/write/admin) |
kses_* (session) | Customer | Authorization: Bearer | User-context calls; carries the member's role permissions |
at_* (app token) | Customer | Authorization: Bearer | Per-org service-to-service integrations. Required (with a session) by the Comms API, which rejects sk_ keys with 401 |
ak_* (agent key) | Customer | x-api-key | Per-agent key for Comms SSE streaming and message sending |
| RS256 JWT | Customer | Authorization: Bearer | Frontend/user flows via the auth service |
st_* (service token) | Customer | x-api-key | Org-scoped automation credential for CI / deploy / webhook workloads (see Service Tokens below). Minted with a session under keys.manage |
sk_admin_* | Operator | varies | Platform operations — only via @kapable/ops-sdk, never the customer SDK |
API Key Authentication
API keys are the simplest way to authenticate. Pass the key in the
x-api-key header on every request.
# API key auth
curl https://api.kapable.ai/v1/me \
-H "x-api-key: sk_live_abc123..."
Getting an API Key
Mint keys with your session via the auth API, or manage them in the console
under Settings → API Keys. Every key-management call is
session-gated and requires the keys.manage permission
— owners and admins hold it by default, and an owner can delegate it to a
custom role (see Roles & Permissions).
# Create. Optional fields: expires_at (absolute RFC3339 timestamp; omit = never),
# project_id / workspace_id (bind the key to a project or workspace).
curl -s -X POST https://api.kapable.ai/v1/auth/api-keys \
-H "Authorization: Bearer kses_..." \
-H "Content-Type: application/json" \
-d '{"name":"production","scopes":["read","write"],"expires_at":"2027-01-01T00:00:00Z"}'
# → { "id": "...", "key_prefix": "sk_live_...", "last_used_at": null, "secret": "sk_live_..." } ← secret shown ONCE
# List (prefixes + last_used_at, never the secret) and revoke:
curl -s https://api.kapable.ai/v1/auth/api-keys -H "Authorization: Bearer kses_..."
curl -s -X DELETE https://api.kapable.ai/v1/auth/api-keys/{id} -H "Authorization: Bearer kses_..."
Rotating a Key
Rotate mints a fresh secret and prefix onto the same key row, so the
key id, scopes, and bindings are preserved. The optional
overlap_hours (0–72, default 0) is the grace window the
previous secret keeps validating — use it to migrate live callers without
an outage; omit it (or send 0) to kill the old secret immediately, the
compromised-key path.
# Immediate cutover (default) — bodyless POST:
curl -s -X POST https://api.kapable.ai/v1/auth/api-keys/{id}/rotate \
-H "Authorization: Bearer kses_..."
# 24-hour grace window on the old secret:
curl -s -X POST https://api.kapable.ai/v1/auth/api-keys/{id}/rotate \
-H "Authorization: Bearer kses_..." \
-H "Content-Type: application/json" \
-d '{"overlap_hours":24}'
# → { "id": "...", "key_prefix": "sk_live_...", "secret": "sk_live_..." } ← NEW secret, shown ONCE
An API key is bound to your org and carries scopes
(read/write/admin), not your member
role's permissions. Scope-gated surfaces (auth, AI, the
Data API, some board routes) accept keys
directly; permission-gated surfaces (board stories, knowledge) return
403 permission required for keys — use session auth there.
A key may optionally be bound to a project at mint time
({"project_id":"…"}) to pin its Data-API schema. Never expose
keys in client-side code or commit them to version control.
SDK Usage
const client = new KapableClient({
baseUrl: 'https://api.kapable.ai',
apiKey: process.env.KAPABLE_API_KEY,
});
// Rust SDK
let client = KapableClient::new("https://api.kapable.ai")
.api_key(std::env::var("KAPABLE_API_KEY").unwrap())
.build();
JWT Bearer Token
For user-context requests (e.g. from a frontend), use a JWT token obtained
via the Auth service's login endpoint. Pass it in the Authorization
header.
# Bearer token auth
curl https://api.kapable.ai/v1/board/stories \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Obtaining a Token
// Login. Response fields: session_token, access_token (JWT),
// access_token_expires_in, role, org_id, member_id, email, name.
const res = await client.auth.login({
email: 'user@example.com',
password: '...',
});
// Use the session token (kses_) as the Bearer `token` for subsequent requests.
const authedClient = new KapableClient({
baseUrl: 'https://api.kapable.ai',
token: res.session_token,
});
// Rust SDK
use kapable_sdk::auth::types::LoginRequest;
let resp = client.auth().login(&LoginRequest {
email: "user@example.com".into(),
password: "...".into(),
}).await?;
let authed_client = KapableClient::new("https://api.kapable.ai")
.token(resp.token)
.build();
Org Context
All API calls are scoped to the authenticated org. API keys are org-bound. JWT tokens carry the org ID in the token claims. Multi-org users select their active org during login.
Agent Keys (Comms)
The Comms service supports a third auth model: agent keys.
These are per-agent API keys minted via
POST /v1/agents/{id}/keys that allow agent processes to
authenticate directly for SSE streaming and message sending.
// Mint an agent key
const { key } = await client.comms.mintAgentKey(agentId, {
label: 'production',
});
// Use it to connect the agent's SSE stream
const stream = new EventSource(
`https://api.kapable.ai/v1/agents/${agentId}/stream`,
{ headers: { 'x-api-key': key } }
);
// Rust SDK
use kapable_sdk::comms::types::MintAgentKeyRequest;
let resp = client.comms().mint_agent_key(agent_id, MintAgentKeyRequest {
label: "production".into(),
}).await?;
println!("Agent key: {}", resp.key);
App Tokens (Comms)
For service-to-service integration with the Comms endpoints, use
app tokens (at_* prefix). These are org-scoped
tokens for programmatic access to intercept and send operations. The Comms
customer surface requires a session or an at_ app token
— it rejects sk_ API keys with 401.
// Mint an app token for your integration
const { token } = await client.comms.mintAppToken(orgId, {
label: 'email-worker',
});
// Rust SDK
use kapable_sdk::comms::types::MintAppTokenRequest;
let resp = client.comms().mint_app_token(org_id, MintAppTokenRequest {
label: "email-worker".into(),
}).await?;
Service Tokens
Service tokens (st_*) are long-lived, org-scoped automation
credentials for CI, deploy, and webhook workloads. They are minted with your
session and gated by the same keys.manage permission as API keys.
Manage them in the console under Settings → Service Tokens
or via the auth API:
# Create. token_type ∈ { ci, deploy, webhook, ... } sets the secret prefix
# (ci → st_ci_, deploy → st_deploy_, webhook → st_wh_, anything else → st_svc_).
# expires_in_days is a RELATIVE day count (1–365, default 90).
curl -s -X POST https://api.kapable.ai/v1/auth/service-tokens \
-H "Authorization: Bearer kses_..." \
-H "Content-Type: application/json" \
-d '{"name":"ci-runner","token_type":"ci","scopes":["read","write"],"expires_in_days":90}'
# → { "id": "...", "key_prefix": "st_ci_...", "secret": "st_ci_..." } ← secret shown ONCE
# List (metadata + last_used_at) and revoke (idempotent — 200 even if already revoked):
curl -s https://api.kapable.ai/v1/auth/service-tokens -H "Authorization: Bearer kses_..."
curl -s -X DELETE https://api.kapable.ai/v1/auth/service-tokens/{id} -H "Authorization: Bearer kses_..."
The internal types dispatch-minter and ci-dispatch
are reserved for platform provisioning and are rejected with 403
on this endpoint. Everything else is free-form and falls back to the
st_svc_ prefix.
Org Auth Settings (Console)
Beyond API credentials, org owners and admins configure the organisation's sign-in experience and account-security posture in the console under Auth Settings:
- Login-page branding — four layouts (centered, left panel, right panel, full background), a custom logo, a background image, and light or dark mode.
- Session policies — session lifetime (default 24 hours), an optional idle timeout that auto-logs-out inactive sessions, and an account-lockout threshold after repeated failed logins (default 5 attempts).
- Multi-factor authentication — members enroll TOTP (Google Authenticator, Authy, …) with backup codes; MFA can be optional or enforced for the whole org.
- Password policy — minimum length and required character classes (uppercase, numbers, special characters).
Member access levels (owner / admin / member / viewer, plus custom roles with granular permissions) are covered in Roles & Permissions.
Error Responses
Authentication failures return 401 Unauthorized. Two envelope
variants exist across services — handle both (the SDKs normalize them
into KapableError for you):
// Most services (data, board, knowledge, ai, ...):
{ "error": { "code": "UNAUTHORIZED", "message": "missing auth token" } }
// Auth service (flat variant):
{ "error": "UNAUTHORIZED", "message": "authentication required via x-api-key, Authorization: Bearer, or session cookie" }