@kapable/sdk (0.23.0)

Published 2026-09-25 15:26:20 +00:00 by kapable

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
Details
npm
2026-09-25 15:26:20 +00:00
10
MIT
latest
286 KiB
Assets (1)
sdk-0.23.0.tgz 286 KiB
Versions (47) View all
0.23.0 2026-09-25
0.22.1 2026-09-20
0.22.0 2026-09-15
0.21.2 2026-09-13
0.21.1 2026-09-05