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

# Node SDK

> TypeScript / Node.js client for pre.dev Browser Agents — run tasks sync, async, or streamed.

The official TypeScript client for pre.dev. `predev-api` ships both the Architect and Browser Agents surfaces — one package, one key, full type support.

## Installation

```bash theme={null}
npm install predev-api
```

## Quick Start

```typescript theme={null}
import { PredevAPI } from 'predev-api';

const predev = new PredevAPI({ apiKey: process.env.PREDEV_API_KEY! });

const result = await predev.browserAgent([
  {
    url: 'https://example.com',
    instruction: 'Extract the page heading.',
    output: {
      type: 'object',
      properties: { heading: { type: 'string' } },
      required: ['heading'],
    },
  },
]);

console.log(result.results[0].data);
// → { heading: "Example Domain" }
```

## Authentication

<Card title="Get your API key" icon="key" href="https://pre.dev/projects/browser-agents" horizontal>
  Grab a key from the Browser Agents dashboard.
</Card>

```typescript theme={null}
const predev = new PredevAPI({ apiKey: 'YOUR_API_KEY' });
```

## API Methods

### `browserAgent()`

Submit one or more browser tasks. Overloaded based on `stream`:

```typescript theme={null}
// Sync / async → Promise<BrowserAgentResponse>
predev.browserAgent(tasks, { concurrency?: number; async?: boolean }): Promise<BrowserAgentResponse>;

// Streaming → AsyncGenerator<BrowserAgentSSEMessage>
predev.browserAgent(tasks, { concurrency?: number; stream: true }): AsyncGenerator<BrowserAgentSSEMessage>;
```

**Parameters:**

* `tasks` **(required)**: `BrowserTask[]` — see [Task shape](#task-shape)
* `options.concurrency` **(optional)**: `number` — parallel workers (1–20, default 5)
* `options.async` **(optional)**: `boolean` — return immediately with a batch id; poll with `getBrowserAgent()`
* `options.stream` **(optional)**: `true` — return an async generator of SSE frames instead

### Task shape

```typescript theme={null}
interface BrowserTask {
  url: string;                           // required
  instruction?: string;                  // natural-language goal
  input?: Record<string, string>;        // string inputs the agent uses during the run
  output?: Record<string, any>;          // JSON Schema for the data to extract
}
```

### `getBrowserAgent()`

Fetch the status and results of a task submission by id. Works for in-progress and completed submissions.

```typescript theme={null}
const result = await predev.getBrowserAgent(id, { includeEvents?: boolean });
```

**Parameters:**

* `id` **(required)**: `string` — id returned from `browserAgent(tasks, { async: true })` or from streaming
* `options.includeEvents` **(optional)**: `boolean` — include the full per-step event timeline (navigation, plan, screenshot, action, validation). Payloads can be large.

## Response Types

```typescript theme={null}
interface BrowserAgentTaskResult {
  url: string;
  status:
    | 'SUCCESS'
    | 'PENDING'
    | 'ERROR'
    | 'TIMEOUT'
    | 'BLOCKED'
    | 'CAPTCHA_FAILED'
    | 'LOOP'
    | 'NO_TARGET';
  data?: unknown;
  creditsUsed: number;
  durationMs: number;
  error?: string;
}

interface BrowserAgentResponse {
  id: string;
  total: number;
  completed: number;
  results: BrowserAgentTaskResult[];
  totalCreditsUsed: number;
  status: 'processing' | 'completed' | 'failed';
  createdAt?: string;
  completedAt?: string;
}
```

## Streaming Events

```typescript theme={null}
type BrowserAgentEventType =
  | 'navigation'    // agent loaded a URL
  | 'screenshot'    // frame captured
  | 'plan'          // agent reasoned about next steps
  | 'action'        // click / type / scroll / extract
  | 'validation'    // output schema or success condition checked
  | 'done'          // task finished
  | 'error';        // task errored

interface BrowserAgentStreamEvent {
  taskIndex: number;
  type: BrowserAgentEventType;
  timestamp: number;
  iteration?: number;
  data: any;
}

type BrowserAgentSSEMessage =
  | { event: 'task_event';  data: BrowserAgentStreamEvent }
  | { event: 'task_result'; data: BrowserAgentTaskResult & { taskIndex: number } }
  | { event: 'done';        data: BrowserAgentResponse }
  | { event: 'error';       data: { error: string } };
```

## Examples

### Sync — wait for every task

```typescript theme={null}
const result = await predev.browserAgent(
  [
    { url: 'https://news.ycombinator.com', instruction: 'Extract the top 5 story titles.' },
    { url: 'https://www.reddit.com/r/programming', instruction: 'Extract the top 5 post titles.' },
  ],
  { concurrency: 2 },
);

for (const r of result.results) {
  console.log(r.status, r.data);
}
```

### Async — poll for completion

```typescript theme={null}
const { id } = await predev.browserAgent(tasks, { async: true });

while (true) {
  const status = await predev.getBrowserAgent(id);
  if (status.status === 'completed' || status.status === 'failed') {
    console.log(status.results);
    break;
  }
  console.log(`${status.completed}/${status.total} done`);
  await new Promise(r => setTimeout(r, 5000));
}
```

### Streaming — live per-step events

```typescript theme={null}
for await (const msg of predev.browserAgent(tasks, { stream: true })) {
  if (msg.event === 'task_event') {
    console.log(`[task ${msg.data.taskIndex}]`, msg.data.type);
  } else if (msg.event === 'task_result') {
    console.log(`[task ${msg.data.taskIndex}]`, msg.data.status, msg.data.data);
  } else if (msg.event === 'done') {
    console.log('batch done:', msg.data.id, msg.data.totalCreditsUsed, 'credits');
  } else if (msg.event === 'error') {
    console.error('error:', msg.data.error);
  }
}
```

### Full timeline for a past submission

```typescript theme={null}
const result = await predev.getBrowserAgent(id, { includeEvents: true });

for (const task of result.results) {
  console.log(task.status, task.url);
  for (const event of (task as any).events ?? []) {
    console.log('  -', event.type);
  }
}
```

## Error Handling

The SDK throws typed exceptions for the most common gating cases. The two
billing-gate exceptions carry an `actionUrl` — a deep link back to pre.dev
that auto-opens the right modal (subscribe / buy credits) when the user
lands there. Same exceptions fire on REST and SSE error paths.

```typescript theme={null}
import {
  PredevAPIError,
  AuthenticationError,
  RateLimitError,
  SubscriptionRequiredError,
  InsufficientCreditsError,
  QueueFullError,
  BatchTooLargeError,
} from 'predev-api';

try {
  const result = await predev.browserAgent(tasks);
} catch (err) {
  if (err instanceof InsufficientCreditsError) {
    // Subscription is fine, balance is too low — send the user to the credits modal.
    if (err.actionUrl) window.open(err.actionUrl, '_blank');
  } else if (err instanceof SubscriptionRequiredError) {
    // Trial limit hit — send the user to the subscribe modal.
    if (err.actionUrl) window.open(err.actionUrl, '_blank');
  } else if (err instanceof AuthenticationError) {
    // 401 — invalid or missing API key
  } else if (err instanceof RateLimitError) {
    // 429 RATE_LIMITED — back off and retry
  } else if (err instanceof QueueFullError) {
    // 429 QUEUE_FULL — wait for in-flight tasks to drain
  } else if (err instanceof BatchTooLargeError) {
    // 400 — split into smaller batches
  } else if (err instanceof PredevAPIError) {
    console.error(err.message);
  }
}
```

| Exception                   | HTTP  | `code`                  |
| --------------------------- | ----- | ----------------------- |
| `SubscriptionRequiredError` | 402   | `SUBSCRIPTION_REQUIRED` |
| `InsufficientCreditsError`  | 402   | `INSUFFICIENT_CREDITS`  |
| `RateLimitError`            | 429   | `RATE_LIMITED`          |
| `QueueFullError`            | 429   | `QUEUE_FULL`            |
| `BatchTooLargeError`        | 400   | `BATCH_TOO_LARGE`       |
| `AuthenticationError`       | 401   | —                       |
| `PredevAPIError`            | other | —                       |

Mid-stream errors on the SSE stream throw the same typed exceptions — a
`for await` loop wrapped in `try/catch` is enough.
