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

# TypeScript SDK

> The stablebrowse TypeScript client: `npm install @stablebrowse/client`

## Install

```bash theme={null}
npm install @stablebrowse/client
```

Node 18+ required. Uses native `fetch` — no runtime dependencies. Works in Node, Deno, Cloudflare Workers, Bun, modern browsers.

## Configuration

```ts theme={null}
import { Stablebrowse } from "@stablebrowse/client";

// Explicit
const client = new Stablebrowse({ apiKey: "sb_live_..." });

// Or read from env (Node only):
//   STABLEBROWSE_API_KEY   — the bearer token
//   STABLEBROWSE_BASE_URL  — override API base
const client = new Stablebrowse();
```

## Running tasks

```ts theme={null}
const result = await client.tasks.run({
  endUserId: "alice",
  task: "Give me the top 3 Show HN posts from today",
});
console.log(result.result);
```

`run()` submits + polls internally. For manual control:

```ts theme={null}
const submission = await client.tasks.submit({
  endUserId: "alice",
  task: "...",
});
while (true) {
  const task = await client.tasks.get(submission.taskId);
  if (["completed", "failed", "cancelled"].includes(task.status)) break;
  await new Promise(r => setTimeout(r, 2000));
}
console.log(task.result);
```

### All task options

```ts theme={null}
const result = await client.tasks.run({
  endUserId: "alice",
  task: "Summarize this article",
  sessionId: "s_abc...",                // continue existing session
  startUrl: "https://example.com/x",    // skip URL-discovery
  schema: { type: "object" /* ... */ }, // structured output
  maxSteps: 10,                         // ≤ 25, server-clamped
  includeHtmlDump: true,                // return raw HTML
  pollIntervalMs: 2000,
  pollTimeoutMs: 300_000,
});
```

## Sessions / follow-ups

```ts theme={null}
const first = await client.tasks.run({
  endUserId: "alice",
  task: "Who's trending on HN?",
});
const follow = await client.tasks.run({
  endUserId: "alice",
  task: "What's the score on the top one?",
  sessionId: first.sessionId,
});
```

Fetch every turn:

```ts theme={null}
const session = await client.sessions.get(first.sessionId!);
for (const t of session.tasks) {
  console.log(t.status, t.task);
}
```

## Design extraction

Pull a site's images, fonts, colors, icons, design tokens, and logo as structured data. See [Design extraction](/concepts/design-extraction) for what each extractor returns.

```ts theme={null}
const task = await client.design.run({
  url: "https://www.figma.com/",
  endUserId: "alice",
  extractors: ["colors", "fonts", "logo"], // omit to run all six
  enableIpRotation: true,                  // optional
});
console.log(task.design?.results.colors);
```

`run()` submits + polls until the extraction terminates (typically 5–15 seconds). For manual control:

```ts theme={null}
const submission = await client.design.submit({
  url: "https://www.figma.com/",
  endUserId: "alice",
  enableIpRotation: true,
});
// ... later — design tasks ride the same task record as agent tasks
const task = await client.tasks.get(submission.taskId);
if (task.status === "completed") console.log(task.design);
```

### Options

```ts theme={null}
const task = await client.design.run({
  url: "https://www.figma.com/",
  endUserId: "alice",
  extractors: ["images", "fonts", "colors", "icons", "tokens", "logo"],
  enableIpRotation: true,
  pollIntervalMs: 2000,
  pollTimeoutMs: 300_000,
});
```

`enableIpRotation` defaults to `false`. Set it to `true` to route the extraction through the residential proxy/IP rotation pool. `task.design` carries the raw JSON the server returns — see the [response schema](/api-reference/design/extract#get-task-response-completed). Asset URLs are signed S3 links valid for 7 days; re-submit the task to refresh.

## End-user credentials

```ts theme={null}
// Upload — only platforms you pass are touched
await client.endUsers("alice").credentials.set({
  twitterAuthToken: "...",
  twitterCt0: "...",
});

// Status (never returns secrets)
const status = await client.endUsers("alice").credentials.get();
// status.platforms.twitter === true

// Clear
await client.endUsers("alice").credentials.delete(["twitterAuthToken", "twitterCt0"]);
await client.endUsers("alice").credentials.delete();  // wipe all
```

## API key management

Create and revoke API keys in the [dashboard](https://main.d30scu9fjpgpae.amplifyapp.com) under **Settings → API Keys**. Key lifecycle is dashboard-only by design — so a leaked key can't mint more.

## Exceptions

```ts theme={null}
import { StablebrowseError, TaskFailed, TaskTimeout } from "@stablebrowse/client";

try {
  const result = await client.tasks.run({ endUserId: "alice", task: "..." });
} catch (e) {
  if (e instanceof TaskFailed) {
    console.log("Agent reported failure:", e.task.error);
  } else if (e instanceof TaskTimeout) {
    console.log("Poll deadline reached, task still", e.task.status);
  } else if (e instanceof StablebrowseError) {
    console.log("HTTP/transport error:", e.statusCode, e.body);
  } else {
    throw e;
  }
}
```

## Typed results

Every response type is a plain interface. Unknown server fields are preserved on `.raw` so future additions don't break you:

```ts theme={null}
const task = await client.tasks.get(taskId);
task.status;                // typed
task.result;                // typed
(task.raw as any).someNewField;  // future-proof
```

## Version pinning

The SDK follows semver. During 0.x.y, API may change on minor bumps. Pin conservatively:

```jsonc theme={null}
// package.json
"dependencies": {
  "@stablebrowse/client": "^0.1.2"  // patches only
}
```

## Source

Published on [npm](https://www.npmjs.com/package/@stablebrowse/client). Issues and feature requests to [team@stablebrowse.ai](mailto:team@stablebrowse.ai).
