@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.
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");}
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.
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.
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"); } }}
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:
// Fetch a single page of a job's resultsconst { job, hasMore, next } = await reader.getJob(jobId, { skip: 0, limit: 20 });// Collect every page automaticallyconst 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 jobawait reader.cancelJob(jobId);// Retry failed URLs in a completed jobconst retryInfo = await reader.retryJob(jobId);console.log(`retrying ${retryInfo.retrying} failed URLs`);
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.