JavaScript and TypeScript SDK
@caplets/sdk is an ESM, Fetch-first client for the public Caplets HTTP API. The root package and
Project Binding coordinator work in Node.js 22+ and modern browsers.
Install
Section titled “Install”npm install @caplets/sdkCreate a client
Section titled “Create a client”Create an isolated client with the absolute HTTP(S) Current Host Origin: scheme, host, and optional port only.
import { createClient, getServiceDiscovery } from "@caplets/sdk";
const client = createClient({ baseUrl: "https://host.example",});
const result = await getServiceDiscovery({ client });if (result.error) { showRequestFailure(result.error, result.response);} else { showService(result.data);}The SDK rejects credentials, non-root paths, queries, and fragments before network I/O; a trailing
root slash is normalized away. Fixed protocol namespaces cannot be relocated through baseUrl.
The SDK does not keep a global client. fetch defaults to globalThis.fetch; pass fetch in the
factory only when you need another Fetch-compatible implementation.
Unauthenticated and bearer clients
Section titled “Unauthenticated and bearer clients”Authentication is optional. A static token or async provider returns the bare token; the SDK adds the bearer header for secured operations.
import { createClient } from "@caplets/sdk";
const publicClient = createClient({ baseUrl: "https://host.example",});
const staticClient = createClient({ baseUrl: "https://host.example", auth: accessToken,});
const asyncClient = createClient({ baseUrl: "https://host.example", auth: async () => getCurrentAccessToken(),});Token storage, Remote Login, refresh, and revocation policy stay with the caller. Never put a bearer
credential in a Current Host or WebSocket URL.
Canonical /api/v2/admin/* operations also declare the host-only dashboard session cookie as an
authentication alternative. The SDK does not bootstrap or persist dashboard sessions. A
same-origin browser that already completed the private dashboard login ceremony may use
credentials: "same-origin" and pass X-Caplets-CSRF on unsafe Admin calls. Supplying auth
selects bearer mode exclusively.
Call generated operation families
Section titled “Call generated operation families”All generated operations and public request, response, and schema types are root exports. Do not import generated files directly.
import { adminV2GetHost, getHealth, type AdminV2GetHostResponse, type Problem } from "@caplets/sdk";
const healthResult = await getHealth({ client });const hostResult = await adminV2GetHost({ client });
if (hostResult.error) { const problem: Problem = hostResult.error; showProblem(problem);} else { const host: AdminV2GetHostResponse = hostResult.data; showHost(host);}Clients default to responseStyle: "fields" and throwOnError: false. The fields result has
data, error, and optional request and response; network failures may not have a response.
Opt into throwing per operation when exception flow is appropriate:
const { data: host, response } = await adminV2GetHost({ client, throwOnError: true,});A successful throwing call remains in fields mode and returns data, request, and response.
Configure throwOnError on the isolated client to change its default for all calls.
Stream Caplet Bundles
Section titled “Stream Caplet Bundles”Bundle helpers at the package root preserve ordered, manifest-first multipart behavior and avoid buffering file bodies.
Download without buffering
Section titled “Download without buffering”import { adminV2GetCapletRecordBundleStream } from "@caplets/sdk";
const result = await adminV2GetCapletRecordBundleStream({ client, path: { id: recordId }, signal,});
if (result.error) { showProblem(result.error);} else if (result.data) { const reader = result.data.getReader(); // Consume reader.read() chunks, or call reader.cancel().}Upload browser files
Section titled “Upload browser files”The FormData helper keeps manifest-first ordering. Do not set Content-Type; Fetch owns the
multipart boundary.
import { adminV2PutCapletRecordBundleFormData, createOrderedBundleFormData } from "@caplets/sdk";
const body = createOrderedBundleFormData(manifestJson, selectedFiles);const result = await adminV2PutCapletRecordBundleFormData({ client, path: { id: recordId }, headers: { "Idempotency-Key": idempotencyKey }, body,});Upload async byte streams
Section titled “Upload async byte streams”Provide repeatable async chunk sources and a caller-generated unique multipart boundary. Each
open callback receives the multipart body’s AbortSignal; the source must stop pending reads and
release resources when that signal is aborted. Zero-argument open callbacks remain compatible.
import { adminV2PutCapletRecordBundleStream, createOrderedBundleMultipartBody } from "@caplets/sdk";
const multipart = createOrderedBundleMultipartBody( manifestJson, [{ open: (bodySignal) => openFileChunks({ signal: bodySignal }) }], multipartBoundary,);
const result = await adminV2PutCapletRecordBundleStream({ client, path: { id: recordId }, headers: { "Idempotency-Key": idempotencyKey }, ...multipart,});The stream upload helper applies Node Fetch’s required half-duplex request option. Canceling the
multipart body aborts its chunk-source signal before closing the active iterator. Pass an
AbortSignal to the operation to cancel the HTTP upload or download.
Run a Project Binding session
Section titled “Run a Project Binding session”@caplets/sdk/project-binding is browser-safe. It requires both the HTTP client and the exact
ws: or wss: /api/v1/attach/project-bindings/connect endpoint. The WebSocket endpoint is
independent input, not something the SDK derives from the Current Host Origin.
import { createClient } from "@caplets/sdk";import { runProjectBindingSession } from "@caplets/sdk/project-binding";
const client = createClient({ baseUrl: "https://host.example", auth: async () => getCurrentAccessToken(),});
const result = await runProjectBindingSession({ client, webSocketUrl: "wss://host.example/api/v1/attach/project-bindings/connect", projectRoot: selectedProjectRoot, projectFingerprint, signal, onEvent(event) { switch (event.type) { case "state": showState(event.state, event.syncState); break; case "ready": showReady(event.bindingId, event.syncState); break; case "reconnecting": showReconnect(event.attempt); break; case "heartbeat": showHeartbeat(event.state, event.syncState); break; case "ended": showEnded(event.reason); break; } },});
if (result.error) { showBindingFailure(result.error.kind, result.error.cleanup);} else { showCompletedBinding(result.data.bindingId);}A browser caller must supply a stable project fingerprint from its project-selection boundary. The filesystem helper is deliberately Node-only; compute there, then pass the result to the same browser-safe coordinator:
import { runProjectBindingSession } from "@caplets/sdk/project-binding";import { fingerprintProjectRoot } from "@caplets/sdk/project-binding/node";
const projectRoot = process.cwd();const projectFingerprint = fingerprintProjectRoot(projectRoot);
const result = await runProjectBindingSession({ client, webSocketUrl: "wss://host.example/api/v1/attach/project-bindings/connect", projectRoot, projectFingerprint,});The coordinator emits state, ready, reconnecting, heartbeat, and ended. It strictly
validates messages, sends HTTP and WebSocket heartbeats every 15 seconds, reconnects once after an
unexpected socket close or error, and runs guarded finalization once. These protocol timings are not
configuration knobs.
By default, the terminal promise returns fields-style session data or a
ProjectBindingSessionError. Error kinds cover http, protocol, socket, callback, aborted,
and cleanup. An initiating error remains primary; a safe cleanup failure can be attached as
secondary detail. With throwOnError: true, the promise returns session data directly or rejects.
Success is reported only after remote terminal state is confirmed; abort is a typed failure after
cleanup.
Bearer authentication uses HTTP headers and WebSocket subprotocol negotiation. It is never added to the URL, event data, or error messages.
Runtime support and scope
Section titled “Runtime support and scope”| Export | Runtime |
|---|---|
@caplets/sdk |
Node.js 22+ and modern browsers with Fetch APIs |
@caplets/sdk/project-binding |
Node.js 22+ and modern browsers with WebSocket APIs |
@caplets/sdk/project-binding/node |
Node.js 22+ only; uses filesystem and crypto APIs |
The generated root client covers public API discovery, health, Remote Login, Attach, Project Binding
HTTP controls, and Admin routes at their canonical /api/* paths. It does not generate
well-known discovery, MCP operations, AsyncAPI clients, dashboard-private cookie/session/CSRF routes,
or Raw Vault Reveal. It also does not provide a credential store or automatic endpoint discovery,
and it does not retry requests at another path or infer a deployment prefix.
Use public root exports; generated internal paths are not a supported API.
See Project Binding for host behavior, synchronization, and operational failures.