Skip to content

SDK Overview

WarmHub has two typed SDKs — @warmhub/sdk-ts for TypeScript and warmhub for Python. Both provide methods for managing organizations and repositories, reading and writing repository data, and accessing the rest of the WarmHub surface — auth, access checks, commits, components, subscriptions, actions, tokens, credentials, diagnostics, live feeds, and homepage.

The two SDKs are versioned independently — the package version numbers are not expected to match.

This page covers what both SDKs share: installation, client setup, client options, and error handling, each with a TypeScript and a Python tab. Everything specific to one language — its guides, reference, and changelog — starts from that language’s page: TypeScript SDK or Python SDK.

WarmHub data is modeled as things (versioned named entities), assertions (claims about things), and shapes (schemas that define the structure of both). If those terms are new, skim Core Concepts before continuing.

In short:

Terminal window
npm install @warmhub/sdk-ts

Runtime requirement: Node.js 22.2 or later is required. Streaming submissions reject older runtimes with the message “Streaming submissions require Bun or Node.js 22.2 or newer.”

Install the WarmHub CLI, then create a personal access token and export it as WH_TOKEN:

Terminal window
wh auth login
wh token create --name my-app
export WH_TOKEN=eyJhbGciOi...

Then create a client:

import { WarmHubClient } from '@warmhub/sdk-ts'
const client = new WarmHubClient({
auth: { getToken: async () => process.env.WH_TOKEN },
})

The SDK does not read environment variables itself — read WH_TOKEN in your own code and pass it to getToken.

See Environment Variables for the variables the CLI honors.

OptionTypeDescription
auth.getToken() => Promise<string | undefined>Token acquisition hook — required for authenticated endpoints
accessTokenstring | () => string | undefined | Promise<string | undefined>Static token or sync/async provider (alternative to auth.getToken)
apiUrlstringOverride the API URL (defaults to https://api.warmhub.ai)
fetchtypeof fetchCustom fetch implementation
functionLogs'raw' | 'off'Server function log forwarding (defaults to 'off')
client.namestringIdentify requests from your SDK wrapper by name (useful when building a higher-level client on top of @warmhub/sdk-ts)
client.versionstringIdentify requests from your SDK wrapper by version (useful when building a higher-level client on top of @warmhub/sdk-ts)
clientFlagsreadonly string[]Declare client capability flags as an array of strings. Flag names are deployment-defined; the SDK validates their format client-side and sends them with requests so the server can tailor its behavior to the declared capabilities.

For a comparison of when to use the SDK vs the CLI or MCP, see the interface comparison on the Get Started page.

Both SDKs group calls into the same client surfaces, such as client.repo, client.thing, and client.commit. Each has a generated reference for method signatures and per-method descriptions:

To ask whether the current token can read or write a repo, call client.access.resolve(input). Pass a repos array (each entry identified by { orgName, repoName } or by durable ID), an orgs array (each entry identified by { orgName } or { orgDurableId }), or both. The result contains a matching entry for each requested resource with a visible boolean and a scopes array listing the granted permission scopes. A visible row also carries the durable IDs that survive renames: repoDurableId and the owning orgDurableId on repo rows, and orgDurableId on org rows. An org row for a durable-ID request echoes orgDurableId and carries no orgName.

For example, to check whether the current token has write access to a specific repo:

import { isKnownRepoAuthScope } from '@warmhub/sdk-ts'
// Identify the repo by name:
const result = await client.access.resolve({
repos: [{ orgName: 'acme', repoName: 'world' }],
})
// Or identify the repo by durable ID:
// const result = await client.access.resolve({
// repos: [{ repoDurableId: 'repo_abc123' }],
// })
const repoAccess = result.repos?.[0]
if (repoAccess?.visible) {
const canWrite = repoAccess.scopes.includes('repo:write')
// proceed based on canWrite
// To branch safely on known scopes without breaking on future additions:
const knownScopes = repoAccess.scopes.filter(isKnownRepoAuthScope)
}
// Note: a result row for a durable-ID request echoes repoDurableId as its
// correlation key and may not carry orgName / repoName — guard before reading.

The scopes array is open-vocabulary — new scope strings may appear as the backend evolves. Use the isKnownRepoAuthScope and isKnownOrgAuthScope narrowing helpers (TypeScript) when you need to branch on specific known members. Known repository scopes include repo:read, repo:checkpoint-read, repo:checkpoint-generate, repo:write, repo:configure, repo:admin, and repo:action-callback. A result row that was requested by durable ID echoes back repoDurableId as its correlation key and carries no orgName or repoName — guard before reading those fields on any result row. For a narrative map of this surface see client.access; the generated WarmHubClient reference is the one place that does not cover it.

Like all SDK methods, resolve throws WarmHubError on network, backend, or auth failures. These checks use the same credentials as the rest of the client; auth and network errors surface the same way as other SDK methods.

Most SDK methods throw WarmHubError on failure (raise WarmHubError in Python). Streamed writes may instead throw PartialStreamSubmissionError for ambiguous append outcomes or AllStreamOperationsFailedError when every submitted operation is rejected with per-op failure data — both exceptions exist in both SDKs; only the TypeScript SDK exports the isAllStreamOperationsFailedError detection helper. See Streaming Write Failures (TypeScript) for the full field contract. Both SDKs expose error-classification helpers:

import { isRetryable, isWarmHubError } from '@warmhub/sdk-ts'
try {
await client.repo.get('acme', 'world')
} catch (err) {
if (isWarmHubError(err) && err.kind === 'NOT_FOUND') {
// handle missing repo
}
if (isRetryable(err)) {
// safe to retry (NETWORK, CANCELLED, BACKEND, RATE_LIMITED)
}
throw err
}

See the ErrorKind reference (TypeScript) for the full per-kind cause, retryability, and corrective action. Common error kinds: NOT_FOUND, VALIDATION_ERROR, CONFLICT, UNAUTHENTICATED, FORBIDDEN, RATE_LIMITED, CANCELLED, NETWORK, BACKEND. Backend domain codes can also pass through unchanged in err.code and err.kind, including ARCHIVED, SHAPE_MISMATCH, and WREF_UNRESOLVABLE.

When a non-streaming write fails, the error may carry a structured details payload. Two details.reason values are worth retrying:

  • expected_version_mismatch: a write that carried an expected version lost a race. Re-read HEAD, reconcile, and resubmit.
  • lease_held: another caller’s lease blocks the write. Wait until leaseExpiresAt, then retry.

One error code is also recoverable, and carries no details: check err.code === 'COMMIT_OUTCOME_UNKNOWN'. The write may or may not have landed, so check whether it did before resubmitting.

Other conflicts, such as NAME_TAKEN or a CONFLICT with no details, are final: resubmitting the same write cannot succeed. See Operation Results for the name_taken arm and the other reasons.

import { isWarmHubError } from '@warmhub/sdk-ts'
try {
await client.commit.apply(/* ... */)
} catch (err) {
if (isWarmHubError(err) && err.details?.reason === 'expected_version_mismatch') {
// Lost a race: HEAD is now at err.details.currentVersion.
// Re-read HEAD, reconcile, and resubmit with the new expected version.
} else if (isWarmHubError(err) && err.details?.reason === 'lease_held') {
// Another caller holds a lease: retry after err.details.leaseExpiresAt.
} else if (isWarmHubError(err) && err.code === 'COMMIT_OUTCOME_UNKNOWN') {
// The write may have landed: check before resubmitting.
}
// Any other conflict, such as NAME_TAKEN, is final: do not retry it.
throw err
}

err.details carries structured conflict metadata when the backend provides it — undefined in TypeScript, None in Python. Not all CONFLICT errors include details; always guard before reading fields.

For streamed writes via client.commit.apply(), the all-failed conflict path surfaces differently: the SDK throws AllStreamOperationsFailedError (in TypeScript, detect it with isAllStreamOperationsFailedError(error) rather than instanceof), which exposes per-operation failure data for every rejected operation. The Transient Retry page documents the full field contract for this error, including receipts, result, operations, statusCounts, and cause.

Each SDK has a changelog page listing every published release, newest first. Entries for breaking changes describe how to migrate.

The same notes also ship inside each package, so you can read the ones for the version you installed offline: CHANGELOG.md and the machine-readable changelog.json at the root of @warmhub/sdk-ts (for example node_modules/@warmhub/sdk-ts/CHANGELOG.md), and inside the warmhub Python package (importlib.resources.files("warmhub") / "CHANGELOG.md").

If you need…TypeScriptPython
Where to start in your languageTypeScript SDKPython SDK
A first read and writeConnect via SDKPython SDK
Task guidesClient Surfaces, Read Semantics, Transient Retry, Component IdentityBootstrap a repository, Paginate, Change drains, Act as another principal, Action runner
Generated referenceTypeScript SDK referencePython SDK reference

Shared by both SDKs:

If you need…Go to…
Choosing the write API that fits the call siteWrite Methods
Repository snapshots and exportsRepository Checkpoints
Exact counts vs dashboard metadata vs per-shape breakdownsRepo Statistics
Terminal-first approachCLI Quickstart
Agent integration via Model Context ProtocolMCP Server