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.
Installation
Section titled “Installation”In short:
npm install @warmhub/sdk-tsRuntime 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.”
pip install warmhubRuntime requirement: Python 3.10 or later. It pulls one runtime dependency, httpx, plus typing-extensions below Python 3.13. Add the re2 extra — pip install "warmhub[re2]" — only if you validate shape pattern constraints in the client.
Client Setup
Section titled “Client Setup”Install the WarmHub CLI, then create a personal access token and export it as WH_TOKEN:
wh auth loginwh token create --name my-appexport 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.
from warmhub import WarmHubClient
with WarmHubClient.from_env() as client: # reads WH_TOKEN ...from_env() is the only constructor that reads the environment, and it says so in the name — it takes WH_TOKEN and WARMHUB_API_URL. The default constructor reads neither; pass a token explicitly with WarmHubClient(access_token=...). An AsyncWarmHubClient with the same surface is available for async/await call sites.
Python also offers a repository handle, so the org and repo aren’t repeated at every call site:
world = client.repository("acme/world")page = world.things.head(shape="Location", limit=5)See Environment Variables for the variables the CLI honors.
Client Options
Section titled “Client Options”| Option | Type | Description |
|---|---|---|
auth.getToken | () => Promise<string | undefined> | Token acquisition hook — required for authenticated endpoints |
accessToken | string | () => string | undefined | Promise<string | undefined> | Static token or sync/async provider (alternative to auth.getToken) |
apiUrl | string | Override the API URL (defaults to https://api.warmhub.ai) |
fetch | typeof fetch | Custom fetch implementation |
functionLogs | 'raw' | 'off' | Server function log forwarding (defaults to 'off') |
client.name | string | Identify requests from your SDK wrapper by name (useful when building a higher-level client on top of @warmhub/sdk-ts) |
client.version | string | Identify requests from your SDK wrapper by version (useful when building a higher-level client on top of @warmhub/sdk-ts) |
clientFlags | readonly 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. |
| Option | Type | Description |
|---|---|---|
access_token | str | Callable[[], str | Awaitable[str | None] | None] | Static token or sync/async provider (alternative to auth; async form only accepted by AsyncWarmHubClient) |
auth | AuthProvider | Auth provider with a get_token() method (alternative to access_token) |
api_url | str | Override the API URL (defaults to https://api.warmhub.ai) |
http_client | httpx.Client | Custom httpx client (use httpx.AsyncClient for AsyncWarmHubClient) |
function_logs | 'raw' | 'off' | Server function log forwarding (defaults to 'off') |
client | Mapping[str, str] | ClientIdentity | Identify requests from your SDK wrapper by name and version |
client_flags | Sequence[str] | Client feature flags |
There is no fetch parameter; pass a pre-configured httpx.Client as http_client instead. All keyword arguments are snake_case — camelCase spellings raise TypeError.
For a comparison of when to use the SDK vs the CLI or MCP, see the interface comparison on the Get Started page.
API Reference
Section titled “API Reference”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:
- TypeScript: the TypeScript SDK reference covers every export in
@warmhub/sdk-ts, including standalone utilities and types such asnormalizeWref,SDK_VERSION,RetryPolicyOptions, andPartialStreamSubmissionError. TheWarmHubClientreference covers the client’s methods. Client Surfaces is a narrative map of the surfaces. - Python: the Python SDK reference covers every export in the package’s public
warmhub.__all__contract. Client namespace methods take an organization and repository explicitly; repository-bound methods omit them afterclient.repository("org/repo").
Permission Checks
Section titled “Permission Checks”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.# Identify the repo by name:result = client.access.resolve({ "repos": [{"orgName": "acme", "repoName": "world"}],})
# Or identify the repo by durable ID:# result = client.access.resolve({# "repos": [{"repoDurableId": "repo_abc123"}],# })
if result.repos and result.repos[0].visible: can_write = "repo:write" in result.repos[0].scopes # proceed based on can_writeaccess.resolve takes the request payload as a plain mapping, and the payload keys follow the API’s camelCase naming (orgName, repoName, repoDurableId) rather than Python convention. The result is a decoded dataclass, so reading it back is snake_case (org_name, repo_name, repo_durable_id). A result row for a durable-ID request echoes back repo_durable_id as its correlation key and may not include org_name or repo_name — guard before reading those fields.
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.
Error Handling
Section titled “Error Handling”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}from warmhub import WarmHubError, is_retryable
try: client.repo.get("acme", "world")except WarmHubError as err: if err.kind == "NOT_FOUND": ... # handle missing repo if is_retryable(err): ... # safe to retry (NETWORK, CANCELLED, BACKEND, RATE_LIMITED) raiseWarmHubError is an exception, so except WarmHubError is the analogue of the
isWarmHubError(err) guard. err.kind is an alias for err.code. The
recovery predicates is_not_found, is_conflict, is_validation_error, and
is_rate_limited read the same classification without comparing strings.
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.
Structured Conflict Details
Section titled “Structured Conflict Details”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 untilleaseExpiresAt, 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}from warmhub import WarmHubError, is_conflict
try: world.apply(...)except WarmHubError as err: if is_conflict(err) and err.current_version is not None: # Lost a race: HEAD is now at err.current_version. # Re-read HEAD, reconcile, and resubmit with the new expected version. ... elif err.lease_expires_at is not None: # Another caller holds a lease: retry after err.lease_expires_at. ... elif 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. raiseerr.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.
Changelogs and upgrades
Section titled “Changelogs and upgrades”Each SDK has a changelog page listing every published release, newest first. Entries for breaking changes describe how to migrate.
- TypeScript: TypeScript SDK changelog
- Python: Python SDK changelog
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").
Where to Go Next
Section titled “Where to Go Next”| If you need… | TypeScript | Python |
|---|---|---|
| Where to start in your language | TypeScript SDK | Python SDK |
| A first read and write | Connect via SDK | Python SDK |
| Task guides | Client Surfaces, Read Semantics, Transient Retry, Component Identity | Bootstrap a repository, Paginate, Change drains, Act as another principal, Action runner |
| Generated reference | TypeScript SDK reference | Python SDK reference |
Shared by both SDKs:
| If you need… | Go to… |
|---|---|
| Choosing the write API that fits the call site | Write Methods |
| Repository snapshots and exports | Repository Checkpoints |
| Exact counts vs dashboard metadata vs per-shape breakdowns | Repo Statistics |
| Terminal-first approach | CLI Quickstart |
| Agent integration via Model Context Protocol | MCP Server |