> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beta.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

Install @b3os/sdk, call every B3OS product from Node, Bun or Workers, wait on jobs, stream events, handle typed errors and read timing.

`@b3os/sdk` is one client with a namespace per product, generated from the OpenAPI document on top of a hand-written runtime. It ships ESM and CommonJS with type declarations and no runtime dependencies. It needs Node 20.16 or later, Bun, or Cloudflare Workers.

```bash
npm install @b3os/sdk
```

## Create a client

```ts
import { B3OS } from "@b3os/sdk";

const b3os = new B3OS(); // reads B3OS_API_KEY
```

| Option | Environment | Meaning |
|---|---|---|
| `apiKey` | `B3OS_API_KEY` | A `b3os_sk_` API key or a `b3os_pat_` CLI token |
| `baseUrl` | `B3OS_BASE_URL` | The API host, `https://beta-api.b3os.org` by default |
| `nodeUrl` | `B3OS_API_URL` | The node's own URL, set for you inside a sandbox; node operations go there without the key |
| `timeoutMs`, `maxRetries`, `maxRetryDelayMs`, `headers`, `fetch`, `onResponse` | | Per-client defaults; `onResponse` sees every response head |

Inside a B3OS sandbox, `B3OS_API_URL` is `http://127.0.0.1:7700`, so the client calls the node with no key and runs as the sandbox's grant. Account operations (keys, usage, limits, organizations, projects and the pack registry) are served only at the API host, so the client sends those there, with the key if one is set.

The client also has a Unix-socket transport (`socketPath`, `B3OS_SOCKET`) that B3OS's own tools use on the node itself. Your code never runs there and never needs it.

## Call a product

```ts
const page = await b3os.vision.scrape({ url: "https://example.com", formats: ["markdown"] });
console.log(page.markdown);

const sandbox = await b3os.sandboxes.createSandbox({ template: "base" });
await b3os.sandboxes.writeFile(sandbox.id, { path: "/workspace/main.py", mkdir: 1 }, "print(2 + 2)\n");
const result = await b3os.sandboxes.exec(sandbox.id, { cmd: "python3 /workspace/main.py" });
console.log(result.stdout);
await b3os.sandboxes.deleteSandbox(sandbox.id);
```

A method takes, in order: the path parameters, an object of query and header parameters when the operation has any, the request body, and request options. Namespaces are `vision`, `render`, `sandboxes`, `routines`, `actions`, `vault` and `infer`, and for your account `keys`, `usage`, `limits`, `requests`, `webhooks`, `orgs` and `projects`. The [method index](/sdks#method-index) lists every method.

Request options, the last argument of every method: `idempotencyKey`, `timeoutMs`, `maxRetries`, `signal`, `headers` and `traceparent`. Every type in the API is exported, for example `import type { Sandbox, ScrapeRequest } from "@b3os/sdk"`.

For an operation the client does not wrap, `b3os.request({ method, path, query, body })` sends it with the same key, retries and errors.

## Lists

Awaiting a list method returns one page (`data` and `next_cursor`); `for await` walks every item across pages:

```ts
for await (const s of b3os.sandboxes.listSandboxes({ status: "running" })) console.log(s.id);
```

## Jobs

Methods that start a job (crawls, batch scrapes, videos, routine runs) return a handle. Awaited on its own, it gives the job as created. `.wait()` and `.events()`, called on the handle before you await it, shape the creating request itself:

```ts
// Wait: ?wait on the create, then the job's event stream until it ends
const crawl = await b3os.vision.createCrawl({ url: "https://docs.example.com", limit: 50 }).wait();
console.log(crawl.status, crawl.result);

// Or follow its events from the creating request on; the stream resumes after a dropped connection
for await (const event of b3os.vision.createCrawl({ url: "https://docs.example.com" }).events()) {
  if (event.type === "page.succeeded") console.log(event.page?.url);
}

// Any job by its ID
const run = await b3os.jobs.wait("run_01m5t5fr06dx6azc42f4wbzf5s");
console.log(run.status);
```

`.wait()` throws `B3OSJobError`, with the job on `.job`, when the job fails or is canceled; pass `{ throwOnFailure: false }` to get the job back instead. `b3os.jobs` also has `get`, `events` and `cancel` for any job ID.

## Streams

Streams are async iterables:

```ts
for await (const event of b3os.sandboxes.exec(sandbox.id, { cmd: "npm test" }, { stream: true })) {
  if (event.type === "exec.stdout") process.stdout.write(event.data ?? "");
}
```

Job and log streams resume after a dropped connection with `Last-Event-ID`. Command output and chat completions cannot resume, so a drop ends them with the client error `stream_interrupted`. `stream.close()` ends a loop early.

## Errors

A failed call throws `B3OSError`, carrying the problem's `code`, `status`, `type` (the link to its entry on the [errors page](/errors)), `title`, `detail`, `requestId`, `errors` (validation pointers), `problem` (the whole body), `retryAfterMs`, `retryable` and `timing`:

```ts
import { B3OSError } from "@b3os/sdk";

try {
  await b3os.sandboxes.getSandbox("sbx_01m5t68q1b590rj801nbzsqzkk");
} catch (error) {
  if (B3OSError.is(error, "sandbox_not_found")) console.log("gone", error.requestId);
  else throw error;
}
```

Branch on `code`, and keep `requestId` for support and for the [request log](/platform/requests).

Failures no server reported carry codes of their own, which are not on the errors page: `connection_error` (`B3OSConnectionError`), `timeout` (`B3OSTimeoutError`), `aborted` (`B3OSAbortError`), `invalid_response`, `stream_interrupted` and `unsupported_transport`.

## Retries and idempotency

The client retries at most `maxRetries` times, with exponential backoff and jitter, or after `Retry-After`:

- always, when the server refused the request before doing any work: `rate_limited`, `concurrency_limited`, `node_unavailable`, `product_unavailable`, `capacity_unavailable`, `idempotency_request_in_progress`, and refused connections;
- for calls that are safe to repeat (`GET`, `PUT`, `DELETE`, or a call with an idempotency key), also retryable `5xx` answers and dropped connections;
- never after a timeout, and never when `Retry-After` is longer than `maxRetryDelayMs`.

Operations that accept an `Idempotency-Key` get one generated per call, and every retry of that call reuses it. Pass your own to make your own retries safe too:

```ts
await b3os.actions.call(
  { ref: "io.b3os/github/create-issue@1", input: { owner: "acme", repo: "web", title: "Flaky test" } },
  { idempotencyKey: crypto.randomUUID() },
);
```

## Timing

`.withResponse()` returns the result together with the response, including its parsed `Server-Timing`:

```ts
const { data, response } = await b3os.vision.scrape({ url: "https://example.com" }).withResponse();
console.log(data.tier, response.requestId, response.timing?.total_ms, response.timing?.phases);
```

`responseOf(result)` returns the same for any object a call returned, and `response.attempts` counts retries.

## Terminals and CDP

```ts
const pty = await b3os.sandboxes.openPty(sandbox.id, { cols: 120, rows: 40 });
pty.onData((bytes) => process.stdout.write(bytes));
pty.write("ls -la\n");
const { code } = await pty.exit;

const cdp = await b3os.vision.connectCdp("brs_01m5t6vhkw4a8fa4tb5w4ffz37"); // URL with a single-use ticket
console.log(code, cdp.url);
```

These open WebSockets, so they need a global `WebSocket` (Node 22 or later, Bun, Workers). Pass `cdp.url` to Playwright's `chromium.connectOverCDP` or Puppeteer's `connect`; see [browser sessions](/vision/sessions#connect-over-cdp).

## Webhooks

`verifyWebhook` from `@b3os/sdk/webhooks` checks a delivery's `B3OS-Signature` and returns the event; see [verify the signature](/concepts/jobs#verify-the-signature).

## Smaller bundles

Each namespace is also a subpath, so a bundle can carry one product:

```ts
import { B3OSCore } from "@b3os/sdk/core";
import { SandboxesAPI } from "@b3os/sdk/sandboxes";

const sandboxes = new SandboxesAPI(new B3OSCore({ apiKey: process.env.B3OS_API_KEY }));
```

The package has no side effects, and a bundle for Workers or browsers contains no `node:` imports.