@skhema/sdk
v0.2.4
Published
Thin typed client for the Skhema Public API (api.skhema.com/v1)
Maintainers
Readme
@skhema/sdk
A thin, typed, zero-runtime-dependency client for the Skhema Public API
(https://api.skhema.com/v1). One method per gateway route, transparent API-key
exchange, and a small, predictable error taxonomy.
Looking for the terminal experience instead? The same API is available as the
@skhema/cliskhemacommand.
Install
npm i @skhema/sdkRequires Node ≥ 18 (uses the global fetch).
Quickstart
import { createSkhemaClient } from '@skhema/sdk'
const skhema = createSkhemaClient({ apiKey: process.env.SKHEMA_API_KEY! })
// The organization is baked into your key — you never pass an org id.
const { workspaces } = await skhema.workspaces.list<{
workspaces: { id: string; name: string }[]
}>()
for (const ws of workspaces) {
console.log(ws.id, ws.name)
}Credential modes
Pass exactly one credential to createSkhemaClient:
| Mode | Pass | Behaviour | Use for |
| -------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| API key | { apiKey: 'sk_live_…' } | Exchanged for a 1-hour JWT lazily on the first call; cached and re-exchanged transparently 60 s before expiry; a mid-window 401 forces one re-exchange + retry. | CI, agents, headless scripts |
| User JWT | { token: '<jwt>' } | Passed through untouched; never refreshed. A 401 throws immediately. | An app that already holds a user OAuth token (e.g. the Skhema CLI) |
// API key (CI / agents)
const skhema = createSkhemaClient({ apiKey: 'sk_live_…' })
// Pre-obtained user JWT (interactive session)
const skhema = createSkhemaClient({ token: userJwt })
// Overrides (staging/preview or tests)
const skhema = createSkhemaClient(
{ apiKey: 'sk_live_…' },
{
baseUrl: 'https://api.staging.skhema.com',
authBaseUrl: 'https://auth.staging.skhema.com',
}
)skhema.getContext() returns { organizationId, permission } observed at the
last API-key exchange (empty in token mode — the org is inside your JWT).
Resource groups
Every method maps to exactly one gateway route.
workspaces— CRUD,members(add/remove/role), andcompliance(list/create/update/delete, matrix, member completion)elements— CRUD,validate/rephrase/fork, andelements.resources(list, upload-url, confirm, download-url, delete).elements.createtakes an optionalcomponentIdto target a specific component instance (list ids viacomponents.list); omitted, the element lands in the first instance of itscomponentTypeby position (auto-created if none exists)links— element links (create/update/delete)components— CRUD,duplicate/reorder/export, andcomponents.links(component-to-component links)strategies— list/get/create/update,commit/activate/exportexports—exports.workspace(...)full-portability export (xlsx/csv/json)webhooks— create/list/delete subscriptionsmembers—members.list()organization membersmeta—health(),openapi()imports— proposal-first URL/file import sessions (create,list,get,confirmUpload,updateDecisions,apply,discard,retry)
Request and response shapes
The SDK is a thin pass-through: the gateway forwards each domain edge function's
JSON unchanged, so every method returns that raw shape typed as T (default
unknown). Supply the concrete type at the call site:
const res = await skhema.elements.list<{ elements: Element[] }>('ws_1')Write methods take a plain JSON body; well-known fields are documented in each
method's JSDoc. For full payloads, pass the object straight through.
Error handling
Every failed call throws a SkhemaApiError (or a subclass), each carrying
status, code, message, and requestId (from x-request-id).
| Class | Status | Notes |
| ------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| AuthError | 401 | Missing/malformed/revoked key, or JWT rejected after re-exchange. Not retried. Also thrown client-side for a key without the sk_live_ prefix. |
| WriteLockedError | 402 | Org can't write (trial expired / past due / cancelled / no plan). Carries state and upgradeUrl. Reads still work. |
| RateLimitedError | 429 | Per-minute budget exhausted. Carries retryAfter (seconds). Idempotent GETs are retried once automatically; writes throw. |
| SkhemaApiError | other | Base class for every other non-2xx (not_found, validation_error, …). |
import { RateLimitedError, WriteLockedError } from '@skhema/sdk'
try {
await skhema.workspaces.create({ name: 'Q3 plan' })
} catch (err) {
if (err instanceof WriteLockedError)
console.error('Upgrade at', err.upgradeUrl)
else if (err instanceof RateLimitedError)
console.error('Retry after', err.retryAfter, 's')
else throw err
}Which client do I want?
| Package | Purpose |
| ------------------------ | ------------------------------------------------------------------------------------------------------------ |
| @skhema/sdk (this) | The Skhema Public API over api.skhema.com/v1. API-key or user-JWT auth. For your code, connectors, and CI. |
| @skhema/cli | The same API as a terminal command (skhema), plus agent onboarding (skhema init). |
| @skhema/agent-sdk | Registered-agent enrollment and request signing for the Skhema agent platform. Not a data-access client. |
The API surface is self-describing: GET https://api.skhema.com/v1/openapi.json
(also available as skhema.meta.openapi()).
License
MIT
