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

# JavaScript SDK

> Install and use @vakra-dev/reader-js

`@vakra-dev/reader-js` is the official TypeScript/JavaScript SDK for the Reader API. It wraps the HTTP contract, parses the response envelope into a discriminated result type, polls async jobs to completion, throws typed errors, and retries transient failures with exponential backoff.

## Installation

```bash theme={null}
npm install @vakra-dev/reader-js
```

Current version: **0.2.0**. Works in Node 18+, Deno, Bun, Cloudflare Workers, and any modern browser.

## Quick start

```typescript theme={null}
import { ReaderClient } from "@vakra-dev/reader-js";

const reader = new ReaderClient({ apiKey: process.env.READER_KEY! });

const result = await reader.read({ url: "https://example.com" });
if (result.kind === "scrape") {
  console.log(result.data.markdown);
  console.log("scraped in", result.data.metadata.duration, "ms");
}
```

<Warning>
  **Do not ship your API key in browser code.** The SDK works in the browser, but exposing `apiKey` client-side means anyone can read and reuse it. Proxy requests through your own backend that holds the key server-side.
</Warning>

## Configuration

```typescript theme={null}
const reader = new ReaderClient({
  apiKey: "rdr_your_key",              // required
  baseUrl: "https://api.reader.dev",   // optional, override for self-hosted
  timeout: 60_000,                     // per-request timeout in ms (default 60s)
  maxRetries: 2,                       // retries on transient failures (default 2)
});
```

## Scraping

### Single URL, synchronous

Single-URL requests return immediately with `{ kind: "scrape", data: ScrapeResult }`.

```typescript theme={null}
const result = await reader.read({
  url: "https://example.com",
  formats: ["markdown"],
  onlyMainContent: true,
});

if (result.kind === "scrape") {
  console.log(result.data.url);                     // canonical URL after redirects
  console.log(result.data.markdown);                // clean markdown
  console.log(result.data.metadata.title);          // page title
  console.log(result.data.metadata.statusCode);     // 200
  console.log(result.data.metadata.duration);       // ms
  console.log(result.data.metadata.cached);         // true if served from cache
  console.log(result.data.metadata.proxyMode);      // "standard" | "premium"
}
```

### Screenshots

Request a screenshot alongside content:

```typescript theme={null}
const result = await reader.read({
  url: "https://example.com",
  formats: ["markdown", "screenshot"],
});

if (result.kind === "scrape" && result.data.screenshot) {
  // screenshot is a base64-encoded PNG
  const buffer = Buffer.from(result.data.screenshot, "base64");
  fs.writeFileSync("screenshot.png", buffer);
}
```

### Multiple URLs (batch)

Passing `urls` creates an async job. The SDK auto-polls until the job terminates and returns `{ kind: "job", data: Job }` with all results collected across pagination.

```typescript theme={null}
const result = await reader.read({
  urls: [
    "https://example.com/page-1",
    "https://example.com/page-2",
    "https://example.com/page-3",
  ],
});

if (result.kind === "job") {
  console.log(`completed ${result.data.completed} / ${result.data.total}`);
  for (const page of result.data.results) {
    if (page.error) {
      console.error(`${page.url}: ${page.error}`);
    } else {
      console.log(page.url, page.markdown?.length, "chars");
    }
  }
}
```

### Crawl

Same shape as batch, but with `maxDepth` or `maxPages`:

```typescript theme={null}
const result = await reader.read({
  url: "https://docs.example.com",
  maxDepth: 3,
  maxPages: 100,
});
```

### Proxy mode

Control how aggressively Reader bypasses bot walls with `proxyMode`:

```typescript theme={null}
// Default: standard. Fast and affordable, works for most sites
await reader.read({ url });

// Explicit standard: force the cheaper tier (error if blocked)
await reader.read({ url, proxyMode: "standard" });

// Explicit premium: residential proxies, bypasses bot walls (3x credits)
await reader.read({ url, proxyMode: "premium" });
```

See [Proxy modes](/home/concepts/proxy-modes) for the full picture.

## Job management

The SDK's `read()` method auto-polls batches and crawls, so most callers never need to touch job APIs directly. When you do, these methods are available:

```typescript theme={null}
// Fetch a single page of a job's results
const { job, hasMore, next } = await reader.getJob(jobId, { skip: 0, limit: 20 });

// Collect every page automatically
const allPages = await reader.getAllJobResults(jobId);

// Poll a job by ID until it terminates (collects all results on completion)
const job = await reader.waitForJob(jobId, {
  pollInterval: 2000,
  timeout: 300_000,
});

// Cancel a queued or processing job
await reader.cancelJob(jobId);

// Retry failed URLs in a completed job
const retryInfo = await reader.retryJob(jobId);
console.log(`retrying ${retryInfo.retrying} failed URLs`);
```

## Streaming

For real-time progress updates on a job, use `reader.stream(jobId)`, an async generator that yields events as the job makes progress.

```typescript theme={null}
for await (const event of reader.stream(jobId)) {
  switch (event.type) {
    case "progress":
      console.log(`${event.completed} / ${event.total}`);
      break;
    case "page":
      console.log("page done:", event.data.url);
      break;
    case "error":
      console.error("page failed:", event.url, event.error);
      break;
    case "done":
      console.log("job finished:", event.status);
      return;
  }
}
```

The generator closes automatically when the job terminates.

## Credits

```typescript theme={null}
const credits = await reader.getCredits();
console.log(`${credits.balance} / ${credits.limit}, tier: ${credits.tier}`);
console.log(`resets at: ${credits.resetAt}`);

if (credits.balance < 100) {
  // Warn, pause workers, upgrade tier, etc.
}
```

## Error handling

Every error response from the API is parsed into a specific `ReaderApiError` subclass. Branch on `instanceof` rather than HTTP status codes.

```typescript theme={null}
import {
  ReaderApiError,
  InvalidRequestError,
  UnauthenticatedError,
  InsufficientCreditsError,
  UrlBlockedError,
  NotFoundError,
  ConflictError,
  RateLimitedError,
  ConcurrencyLimitedError,
  InternalServerError,
  UpstreamUnavailableError,
  ScrapeTimeoutError,
} from "@vakra-dev/reader-js";

try {
  const result = await reader.read({ url });
  // ...
} catch (err) {
  if (err instanceof InsufficientCreditsError) {
    console.error(`Need ${err.required} credits, have ${err.available}`);
    console.error(`Resets at ${err.resetAt}`);
  } else if (err instanceof RateLimitedError) {
    console.error(`Rate limited. Retry after ${err.retryAfterSeconds}s`);
  } else if (err instanceof UrlBlockedError) {
    console.error(`URL blocked: ${err.reason}`);
  } else if (err instanceof ScrapeTimeoutError) {
    console.error(`Scrape exceeded ${err.timeoutMs}ms`);
  } else if (err instanceof ReaderApiError) {
    // Catch-all for known API errors
    console.error(`[${err.code}] ${err.message} (see ${err.docsUrl})`);
    console.error(`Request ID: ${err.requestId}`);
  } else {
    throw err;
  }
}
```

Every error has:

* `code`: one of 11 stable codes (e.g. `"insufficient_credits"`, `"rate_limited"`)
* `message`: human-readable description
* `httpStatus`: the HTTP status code
* `details`: typed payload specific to the error (e.g. `required`/`available` for insufficient\_credits)
* `docsUrl`: deep link to the error's documentation
* `requestId`: the `x-request-id` header from the response, for support tickets

The full catalog is at [Errors](/home/concepts/errors).

### Automatic retries

The SDK retries these codes automatically with exponential backoff before throwing: `rate_limited` (honors `Retry-After`), `concurrency_limited`, `internal_error`, `upstream_unavailable`, `scrape_timeout`. All other codes throw immediately.

## Webhooks per request

Every `read()` call can include an inline webhook config that fires on job lifecycle events, useful for fire-and-forget batches.

```typescript theme={null}
await reader.read({
  urls: manyUrls,
  webhook: {
    url: "https://your-app.example.com/hooks/reader",
    events: ["job.completed", "job.failed"],
    secret: process.env.READER_WEBHOOK_SECRET,
  },
});
```

See [Webhooks](/home/concepts/webhooks) for the full delivery contract and signature verification.

## Types

All public types are re-exported from the package root:

```typescript theme={null}
import type {
  ReaderClientConfig,
  ReadParams,
  ReadResult,      // { kind: "scrape", data: ScrapeResult } | { kind: "job", data: Job }
  ScrapeResult,
  ScrapeMetadata,
  Job,
  JobStatus,
  JobMode,
  Page,
  ProxyMode,       // "standard" | "premium"
  Pagination,
  Credits,
  UsageEntry,
  StreamEvent,
} from "@vakra-dev/reader-js";
```

## Browser Sessions

Create stealthed browser sessions for Playwright/Puppeteer automation:

```typescript theme={null}
import { ReaderClient } from "@vakra-dev/reader-js";
import { chromium } from "playwright-core";

const reader = new ReaderClient({ apiKey: "rdr_..." });

// Create a session
const session = await reader.sessions.create();
console.log(session.wsEndpoint);

// Connect Playwright
const browser = await chromium.connectOverCDP(session.wsEndpoint);
const page = await (await browser.newContext()).newPage();
await page.goto("https://example.com");
console.log(await page.title());

// Cleanup
await browser.close();
await reader.sessions.stop(session.sessionId);
```

### Sessions API

| Method                            | Description                                               |
| --------------------------------- | --------------------------------------------------------- |
| `reader.sessions.create(params?)` | Create a session, returns `SessionInfo` with `wsEndpoint` |
| `reader.sessions.get(sessionId)`  | Get session status                                        |
| `reader.sessions.stop(sessionId)` | Stop a session, returns billing summary                   |
| `reader.sessions.list()`          | List active sessions                                      |

### Session Types

```typescript theme={null}
import type {
  SessionInfo,
  CreateSessionParams,
  StopSessionResult,
  SessionStatus,
} from "@vakra-dev/reader-js";
```

## Next

* [Python SDK](/sdk/python): same features, Pythonic API
* [Browser Sessions](/home/concepts/browser-sessions): concept overview
* [Errors](/home/concepts/errors): full error code catalog
* [Proxy modes](/home/concepts/proxy-modes): when to override the default
* [API reference](/api-reference/read): the raw HTTP contract
