JavaScript / TypeScript SDK
npm install wellmarked — dual ESM and CJS, bundled types, runs on Node, Deno, Bun, Cloudflare Workers, and the browser.
npm install wellmarkedDual ESM + CJS with bundled .d.ts. Runs on Node 18+, Deno, Bun, Cloudflare Workers, and the
browser.
Quickstart
import { WellMarked } from "wellmarked";
const wm = new WellMarked({ apiKey: "wm_..." });
const result = await wm.extract("https://example.com");
console.log(result.markdown);
console.log(`${result.metadata.title} by ${result.metadata.author}`);apiKey falls back to the WELLMARKED_API_KEY environment variable — on Node.js only, since
the other supported runtimes have no process.env.
The surface
import { WellMarked } from "wellmarked";
const wm = new WellMarked({ apiKey: "wm_..." });
// Single extract
const result = await wm.extract("https://example.com");
// Bulk
let job = await wm.bulk(["https://a.example", "https://b.example"]);
job = await wm.waitForJob(job.jobId);
// Crawl — the same waitForJob works on either kind of jobId
job = await wm.crawl("https://docs.example.com", { depth: 2 });
job = await wm.waitForJob(job.jobId);
// Usage + rotate
const usage = await wm.getUsage();
const rotated = await wm.rotateKey(); // the client swaps to the new key for youwaitForJob widens the type
It returns BulkJob | CrawlJob, discriminated by kind. If you reassign a variable across the
call, declare the union up front:
import { WellMarked, type BulkJob, type CrawlJob } from "wellmarked";
let job: BulkJob | CrawlJob = await wm.bulk([...]);
job = await wm.waitForJob(job.jobId);Errors
Exceptions inherit from WellMarkedError:
| Exception | Status |
|---|---|
AuthenticationError | 401 |
PermissionDeniedError | 403 |
NotFoundError | 404 |
UnprocessableEntityError | 422 |
RateLimitError | 429 |
InternalServerError | 5xx |
APIConnectionError | transport failure, no response |
Each carries code, statusCode, retryAfter, retryAfterMs, and requestId.
import { WellMarked, RateLimitError } from "wellmarked";
try {
const result = await wm.extract("https://example.com");
} catch (e) {
if (e instanceof RateLimitError) {
if (e.code === "rate_limit_too_fast") {
await sleep(e.retryAfterMs!);
} else {
// monthly quota spent — retrying will not help
}
}
}See Errors for every code the API can emit.
Retries and idempotency
A submission that fails on a retryable error is retried internally reusing the same
idempotency key, so an SDK-level retry can never double-submit. Tune the attempt count with
maxRetries (default 2).
Webhook verification
import { verifyWebhook, WebhookVerificationError } from "wellmarked";
const payload = await verifyWebhook({
secret: process.env.WELLMARKED_WEBHOOK_SECRET!,
headers: req.headers,
body: rawBody, // bytes, NOT a parsed JSON object
});Built on crypto.subtle, so it works everywhere the client does with no runtime-specific
imports. See Webhooks.