@kapable/sdk (0.23.0)
Installation
@kapable:registry=npm install @kapable/sdk@0.23.0"@kapable/sdk": "0.23.0"About this package
@kapable/sdk
TypeScript SDK for the Kapable v2 platform API. Fetch-based, works in Node, Bun, Deno, and browsers. It is the single, typed, audited mirror of every customer-tier capability on the platform, scoped to the correct security token.
New here? Start at the SDK suite front door:
../docs/sdk.md— which package, which token, the module catalog, and coverage at a glance.
Version 0.21.0. See CHANGELOG.md.
Install
bun add @kapable/sdk
# or
npm install @kapable/sdk
Quick Start
import { KapableClient } from '@kapable/sdk';
const client = new KapableClient({
baseUrl: 'https://api.kapable.ai',
apiKey: 'sk_live_...',
});
// List active stories
const stories = await client.board.listStories({ status: 'active' });
console.log(`${stories.total} stories found`);
// Auto-paginate through all stories
for await (const story of client.board.paginateStories({ status: 'active' })) {
console.log(story.title);
}
// Create a story
const story = await client.board.createStory({
title: 'Implement login flow',
priority: 'high',
product_id: 'product-uuid',
});
// Upload a file
await client.store.uploadObject(
'uploads',
'reports/q3.pdf',
fileBytes,
'application/pdf',
);
Authentication
// API key (sent as x-api-key header)
const client = new KapableClient({
baseUrl: 'https://api.kapable.ai',
apiKey: 'sk_live_...',
});
// JWT / session token (sent as Authorization: Bearer header)
const client = new KapableClient({
baseUrl: 'https://api.kapable.ai',
token: 'jwt_token_here',
});
Picking the right token
The SDK never enforces authorization — the server does — but it ships a typed
description of the platform's RBAC / token model so you can pick the right
credential up front. Import the security surface to inspect required token
types, permissions, and scopes:
import { Permission, TokenType, Scope, TIER, ownerWith, classifyToken } from '@kapable/sdk/security';
classifyToken('sk_live_abc'); // TokenType.ApiKey
const billingReq = ownerWith(Permission.BillingManage); // AuthRequirement
- Token tiering & contribution rules: CONTRIBUTING.md
- Full RBAC / token-tiering matrix + gap analysis: ../docs/sdk-surface-matrix.md
- Operator/admin endpoints (berth, foreman, host, gateway, …) are deliberately
excluded — they belong in the planned
@kapable/ops-sdk, never this package.
Colleagues, runs, and conversations
Member sessions can inspect colleagues, wake one immediately, and read the
resulting runs. Agent-key runners poll the same run queue, claim work, and
report progress through the root runs client:
const { colleagues } = await client.colleagues.list();
const colleague = colleagues[0];
if (!colleague) throw new Error('no colleagues are configured');
const woken = await client.runs.wake(colleague.agent.id, {
message: 'Inspect the current deployment.',
});
const { runs } = await client.runs.list({
agent_id: colleague.agent.id,
limit: 50,
});
const { runs: approvalRuns } = await client.runs.list({
status: 'awaiting_approval',
});
const pendingApproval = approvalRuns[0];
if (pendingApproval?.approval) {
await client.runs.approve(pendingApproval.id, {
request_id: pendingApproval.approval.request_id,
note: 'Reviewed on the floor.',
});
// To decline instead:
// await client.runs.decline(pendingApproval.id, {
// request_id: pendingApproval.approval.request_id,
// });
}
const runner = new KapableClient({
baseUrl: 'https://api.kapable.ai',
token: 'ak_...',
});
const { runs: offers, follow_ups } = await runner.runs.offers({ wait: 25 });
const offer = offers[0];
if (offer) {
const claimed = await runner.runs.claim(offer.id);
await runner.runs.report(claimed.id, {
status: 'running',
receipt: { session_id: 'runner-session-id' },
});
}
approve and decline use a member session and return a bare run row. A
decision made before expiry returns the current run (HTTP 200); answering an
expired card returns the new approval-triggered run (HTTP 201).
follow_ups carries later room messages for active conversations. This SDK has
no standalone rooms client, so idempotent one-to-one room creation is exposed
as client.runs.directRoom({ identity_kind, identity_id }).
Workspace scoping
Workspace-scoped data (workspaces module, workspace_scoped data tables) is
resolved server-side from the X-Workspace-Id header (membership-validated by
kapable-auth; a workspace-bound credential always wins over the header).
The SDK sets the header natively — no header plumbing needed:
// Constructor scope: every request carries X-Workspace-Id
const client = new KapableClient({
baseUrl: 'https://api.kapable.ai',
token: 'jwt_token_here',
workspaceId: 'a4d3…',
});
// Per-request override (string = different workspace, null = org-wide opt-out)
await client.get('/v1/tables/notes/rows', { workspaceId: 'other-ws' });
await client.get('/v1/tables', { workspaceId: null });
// Derive a re-scoped client — sub-clients carry the new scope
const wsClient = client.withWorkspace('a4d3…');
await wsClient.data.listRows('notes');
Signing users in to your app (@kapable/sdk/app-auth)
For code running inside a Kapable app ({app}.{org}.kapable.run) with Kapable
sign-in switched on (the app's auth config has kapable_auto_adopt=true). The
Kapable router signs every visitor in before the request reaches your app and
tells your server who they are in four headers; these helpers read them and
check your app's own permissions. No network calls, no framework lock-in: pass
a Fetch Request, a Node IncomingMessage, Headers, or { headers }.
import { currentUser, requireUser, defineAccess, signOutUrl } from '@kapable/sdk/app-auth';
const user = currentUser(request); // { memberId, orgId, email, role } or null
const me = requireUser(request); // no signed-in user: throws KapableAuthRequired (401)
const access = defineAccess({
roles: {
owner: ['*'], // "*" only where you list it
admin: ['invoices.read', 'invoices.approve'],
member: ['invoices.read'],
'Key keeper': ['invoices.read'], // custom org role names match exactly
},
});
access.can(user, 'invoices.approve'); // boolean; null user → false
access.require(request, 'invoices.approve'); // signed in without it: throws KapableForbidden (403)
signOutUrl(); // "/__kapable/auth/logout"
KapableAuthRequired can only happen when sign-in is off for the app or the
app was reached without the router; its message says so, and its details
name the setting to change.
Both errors carry .status and .response, a ready Fetch Response, so the
framework adapter is one line, e.g. in Hono:
app.onError((e, c) => (e instanceof KapableAuthRequired || e instanceof KapableForbidden ? e.response : c.text('Something went wrong', 500))).
The JSON body is { error: { code, message, details } }: message is the line
a person reads, and details is what to fix (for a 403, the permission and the
member's role).
A role grants nothing in your app until defineAccess lists it. Role names
cannot contain a comma: an identity header holding one is read as a repeated
header, and nobody is signed in. The SDK never invents a user: for local
development, set the four x-kapable-* headers in a dev-only middleware.
These headers are trustworthy only behind the Kapable router, which removes any the visitor sends and writes exactly one of each. Duplicate detection here is best-effort; on Bun, a repeated header line keeps only its last value and cannot be detected. Never expose your app's port directly.
Error Handling
All API errors throw KapableError with code, message, and status fields
matching the standard error envelope:
import { KapableError } from '@kapable/sdk';
try {
await client.board.getStory('nonexistent');
} catch (err) {
if (err instanceof KapableError) {
console.log(err.status); // 404
console.log(err.code); // "NOT_FOUND"
console.log(err.message); // "story not found"
}
}
Real-time (SSE)
Several modules expose text/event-stream endpoints (board events, data row
changes, comms room/agent/anomaly feeds, AI proxy streaming). They return an
async iterable of typed events:
for await (const ev of client.board.streamEvents()) {
console.log(ev.operation, ev.data);
}
The low-level SseClient / parseSseBuffer (from @kapable/sdk/sse) back these
streams and can be used directly against any Kapable SSE endpoint.
Modules
11 customer-tier modules. Method counts are TS / Rust (kept at parity by the
drift gate). The lists below are generated from coverage-manifest.json — the
source of truth for the wrapped surface.
ai — kapable-ai (6 / 6)
Provider catalog, per-org provider config, upstream proxy (SSE-capable).
configureProvider, getProvider, listProviders, proxy, proxyStream, removeProviderConfig
auth — kapable-auth (115 / 109)
Login, signup, orgs, members, API keys, apps, deployments, lanes, workspaces, custom domains, invitations, conductor instances, onboarding, consent.
login, signup, logout, logoutEverywhere, refresh, me, validate, validateHeader, sessionInfo, magicLinkRequest, magicLinkVerify, signInEmail, getSessionBetterAuth, jwks, createApiKey, listApiKeys, revokeApiKey, createServiceToken, getOrg, renameOrg, getOrgCaps, adminCreateOrg, adminCreateOrgApiKey, listOrgMembers, assignMemberRole, deactivateMember, reactivateMember, createInvitation, listInvitations, revokeInvitation, acceptInvitation, listRoles, createRole, updateRole, deleteRole, listPermissions, listOrgApps, getAppDetail, getAppChoice, createAppChoice, forkAppChoice, renameApp, deleteApp, pauseApp, resumeApp, abortPendingApp, publishAppAsTemplate, listLaunchpadApps, requestDeploy, rebuildApp, retryDeployment, deploymentStatusCallback, streamAppLogs, appLogs, listAppEnv, upsertAppEnv, deleteAppEnv, listAppEnvironments, createAppEnvironment, getAppEnvironment, deleteAppEnvironment, listAppEnvVars, createAppEnvVar, updateAppEnvVar, deleteAppEnvVar, listCustomDomains, createCustomDomain, checkCustomDomain, setPrimaryDomain, deleteCustomDomain, getAuthConfig, updateAuthConfig, getTraffic, setTraffic, getActivity, postActivity, getOnboarding, markOnboardingProgress, dismissOnboarding, getConsentStatus, getConsentReport, createWorkspace, listWorkspaces, getWorkspace, updateWorkspace, deleteWorkspace, inviteWorkspaceMember, updateWorkspaceMember, revokeWorkspaceMember, bindWorkspaceResource, unbindWorkspaceResource, listWorkspaceResources, createLane, listLanes, getLane, updateLane, deleteLane, deployToLane, promoteLane, drainLane, registerConductorInstance, listConductorInstances, getConductorInstance, conductorHeartbeat, deleteConductorInstance, … (full list in coverage-manifest.json)
billing — kapable-billing (3 / 3)
Org subscription summary, Stripe checkout init, customer portal. Owner-tier.
get, init, portal
board — kapable-board (42 / 41)
Stories, sprints, plans, products, comments, closures, lanes/gates, evidence/verdict, worktrees + agent-report (Routa coordination), story repos, board event stream.
listStories, paginateStories, getStory, createStory, updateStory, deleteStory, transitionStory, searchStories, listSprints, getSprint, createSprint, startSprint, completeSprint, attachStory, detachStory, getClosure, upsertClosure, listPlans, getPlan, createPlan, updatePlan, listRevisions, createRevision, listProducts, getProduct, createProduct, updateProduct, listComments, createComment, updateComment, deleteComment, getLanes, setLanes, postEvidence, getVerdict, listWorktrees, agentReport, listStoryRepos, addStoryRepo, removeStoryRepo, listRepos, streamEvents
comms — kapable-comms (43 / 43)
Agents, agent keys, mailboxes, channels, rooms, messages, email, app tokens, presence/heartbeat, quotas, Telegram bridges, annotations, and SSE feeds (room / agent / anomaly).
createAgent, listAgents, deleteAgent, mintAgentKey, listAgentKeys, revokeAgentKey, createMailbox, listAgentMailboxes, deleteMailbox, listMailboxChannels, createChannel, getMessage, listMessages, sendEmail, createRoom, listRooms, getRoom, roomPresence, heartbeat, joinRoom, inviteAgentToRoom, revokeRoomAgent, createRoomInvite, listRoomInvites, revokeRoomInvite, postRoomMessage, listRoomMessages, mintAppToken, listAppTokens, revokeAppToken, postAnnotations, upsertMailboxQuota, getMailboxQuotas, deleteMailboxQuota, mailboxRateLimitHits, listOrgSendAudit, linkTelegramBridge, telegramSetupUrl, listBridges, revokeBridge, streamRoom, streamAgent, streamAnomalyFeed
data — kapable-data (18 / 17)
Tables CRUD, rows CRUD, search, bulk operations, CSV import/export, and SSE row change streams.
listTables, createTable, dropTable, migrateTable, listRows, paginateRows, getRow, createRows, updateRow, patchRow, deleteRow, searchRows, bulkUpdate, bulkDelete, importCsv, exportData, subscribeChanges, subscribeOrgChanges
harbor — harbor (11 / 11)
Runtime licensing + release store. Public activate/validate/verify/download +
org-scoped license/usage/keypair reads. (Admin product/feature/tier CRUD is
deferred to @kapable/ops-sdk — see IMP-1366.)
activate, validate, verifyRelease, getOrgPublicKey, getReleaseManifest, getLatestReleaseCompat, downloadReleaseArtifact, downloadArtifact, getLicense, getLicenseUsage, listOrgKeys
kaps — kapable-kaps (53 / Rust pending)
Small deployed apps: files and versions, triggers (request/schedule/inbox), run history, bypass tokens and endpoint secrets, audience, plus each kap's own database, file store and secrets.
Kaps is served on the ORG host, not api.kapable.ai, and accepts a member session only:
import { KapableClient } from '@kapable/sdk';
const client = new KapableClient({
baseUrl: 'https://api.kapable.ai', // used by every other module
token: 'kses_...', // member session; an sk_org_ key is refused
orgBaseUrl: 'https://acme.kapable.ai', // where kaps lives
});
// Create a kap — writes send X-Kapable-Client automatically
const created = await client.kaps.createKap(orgId, {
name: 'hello',
main_file: 'main.ts',
files: [{ path: 'main.ts', content: 'export default () => new Response("hi")' }],
});
// Read its run history
const runs = await client.kaps.listRuns(orgId, created.kap.id, { limit: 20 });
// Query its database
const { rows } = await client.kaps.dbQuery(orgId, created.kap.id, {
sql: 'select * from notes limit 10',
});
Without orgBaseUrl every client.kaps.* call fails fast with an error naming
the fix.
listKaps, createKap, getKap, saveFiles, listVersions, getVersion, diffVersions, restoreVersion, deleteKap, listRecovery, recoverKap, getHistory, listBypassTokens, createBypassToken, revokeBypassToken, listEndpointSecrets, createEndpointSecret, revokeEndpointSecret, listKapTriggers, setRequestTrigger, listTriggers, createTrigger, pauseTrigger, resumeTrigger, deleteTrigger, runTriggerNow, listTriggerRuns, getTriggerRun, acknowledgeTriggerRun, listRuns, getAudience, setAudience, disableKap, enableKap, getUsage, dbQuery, dbTables, dbListRows, dbInsertRow, dbGetRow, dbUpdateRow, dbDeleteRow, storeListFiles, storePutFile, storeGetFile, storeDeleteFile, listSecrets, setSecret, deleteSecret, listSecretGroups, attachSecretGroup, detachSecretGroup, exportManifest
Section B adds branches, remix and change requests plus org-level secret
groups: listBranches, createBranch, getBranch, saveBranchFiles,
listBranchVersions, restoreBranchVersion, mergeBranch (with
preview: true for a dry run), getRemix, retryRemix,
setSourceVisibility, listChangeRequests, createChangeRequest,
applyChangeRequest, listOrgSecretGroups, createOrgSecretGroup,
listGroupSecrets, setGroupSecret, deleteGroupSecret and
groupHistory.
knowledge — kapable-knowledge (38 / 37)
Claims, claim confidence/meta/derivations, edges, predicates, sources, pages, corpora, perspectives, canonical entities, merge candidates, provenance, invariants, assay runs, and tensions.
createClaim, getClaim, listClaims, searchClaims, listClaimsByPerspective, retractClaim, createClaimConfidenceEvidence, listClaimConfidenceEvidence, listAllClaimConfidenceEvidence, createClaimMeta, listClaimMeta, createClaimDerivations, listClaimDerivations, createEdge, listEdges, listPredicates, predicateOntology, createSource, listSources, deleteSource, createPage, listPages, mergePages, createCorpus, getCorpus, listCorpora, getPerspective, listPerspectives, listCanonicalEntities, resolveCanonicalEntity, listMergeCandidates, decideMergeCandidate, unmergeMergeCandidate, listSourceProvenance, sourceProvenanceGraph, listInvariants, listAssayRuns, detectTensions
secrets — strongbox (7 / 7)
Org-scoped secrets vault: CRUD, rotate, audit log.
create, get, list, update, delete, rotate, audit
store — kapable-store (10 / 10)
Buckets, objects, presigned URLs.
listBuckets, createBucket, listObjects, paginateObjects, uploadObject, downloadObject, headObject, deleteObject, presignUpload, presignDownload
wiki — kv2-wiki (31 / 31)
JSON routes only: pages/collections/tags/projects CRUD, prompt-page generation, and the substrate/wiki agent-tools. HTML-rendering routes and the UI SSE stream are deliberately excluded.
createPage, listPages, updatePage, deletePage, getRawPage, createEntityPage, createPromptPage, updatePrompt, regenerate, generateNarrative, pageGenerationHistory, createCollection, listCollections, getCollection, updateCollection, deleteCollection, addPageToCollection, removePageFromCollection, updatePageInCollection, createTag, listTags, addTagsToPage, removeTagFromPage, deleteTag, createProject, listProjects, searchClaims, getCanonicalEntity, listPredicates, readPageTool, listPagesTool
Tree-Shakeable Imports
Every module is exported both from the root and as a ./<module> subpath:
// Import only the board module
import { BoardClient } from '@kapable/sdk/board';
import type { Story, Sprint } from '@kapable/sdk/board';
// Import only the store module
import { StoreClient } from '@kapable/sdk/store';
import type { Bucket, ObjectInfo } from '@kapable/sdk/store';
// The security model, the SSE primitives
import { Permission } from '@kapable/sdk/security';
import { SseClient } from '@kapable/sdk/sse';
Subpaths: ./ai, ./auth, ./billing, ./board, ./comms, ./data,
./harbor, ./knowledge, ./secrets, ./store, ./wiki, ./security, ./sse,
./kaps. ./app-auth is subpath-only: it is for app servers, not API clients.
Coverage & drift gate
Coverage is measured against the customer-tier, SDK-eligible endpoint inventory:
bun run generate-manifest # rebuild coverage-manifest.json from live source + inventory
bun run check-drift # report coverage %, gaps, and any phantom methods (CI gate)
Current: 74.7% customer-tier coverage, 0 phantom methods. The Rust SDK
(kapable-sdk-rs) is kept at method parity by the same manifest.
License
MIT
Customer service tokens
Create and rotate still use /v1/auth/service-tokens from your own session
with keys.manage. New secrets start with sig_st_ and are revealed once.
Metadata reports kind: signet or legacy_hmac; older servers may omit it.
Signet rotation keeps the platform token id; legacy st_ rotation returns a
successor id. List and revoke support both kinds. No admin key is needed in
this SDK or client code.
Signet scopes cannot exceed the member's authority. Workspace bindings are
preserved in the exact scope path; send the matching X-Workspace-Id when
using the token. Project bindings (including default production projects) and
workspace roles currently receive SERVICE_TOKEN_BINDING_UNSUPPORTED instead
of a broader credential. An uncertain mutation requires operator
reconciliation before retrying. Public create/rotate function signatures are
unchanged.
Dependencies
Development dependencies
| ID | Version |
|---|---|
| @types/bun | ^1.2.0 |
| typescript | ^5.7.0 |