Skip to content

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.

Terminal window
npm install @caplets/sdk

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.

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.

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.

Bundle helpers at the package root preserve ordered, manifest-first multipart behavior and avoid buffering file bodies.

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().
}

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,
});

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.

@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.

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.