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

TokenTierHeaderUse
sk_live_* / sk_test_* / sk_org_*Customerx-api-keyServer-to-server, org-bound, scoped (read/write/admin)
kses_* (session)CustomerAuthorization: BearerUser-context calls; carries the member's role permissions
at_* (app token)CustomerAuthorization: BearerPer-org service-to-service integrations. Required (with a session) by the Comms API, which rejects sk_ keys with 401
ak_* (agent key)Customerx-api-keyPer-agent key for Comms SSE streaming and message sending
RS256 JWTCustomerAuthorization: BearerFrontend/user flows via the auth service
st_* (service token)Customerx-api-keyOrg-scoped automation credential for CI / deploy / webhook workloads (see Service Tokens below). Minted with a session under keys.manage
sk_admin_*OperatorvariesPlatform 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
Keys are org-bound and scoped — not omnipotent

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_..."
Reserved token types

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:

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" }