TypeScript client — @webforai/platform
@webforai/platform is the official
typed client for the platform API. It has zero dependencies and runs anywhere fetch
exists: Node ≥18, browsers, Cloudflare Workers and other edge runtimes.
npm i @webforai/platformScrape
import { createPlatformClient } from "@webforai/platform";
const platform = createPlatformClient({ apiKey: process.env.WEBFORAI_API_KEY });
const page = await platform.scrape({
url: "https://example.com/article",
engine: "browser", // fetch | browser | proxy-fetch | proxy-browser
convert: { frontmatter: true },
});
page.markdown; // "# …"
page.metadata; // { title, author, … }
page.credits; // 5Self-hosting? Point the client at your own deployment:
const platform = createPlatformClient({ apiKey, baseUrl: "https://platform.your.domain" });Batch and crawl jobs
Async jobs return a jobId; waitForJob polls until the job reaches a terminal state, and
jobResults iterates every page result — following pagination and downloading oversized
results (which the API stores out-of-band) transparently.
const { jobId } = await platform.crawl({
url: "https://docs.example.com/",
maxDepth: 2,
limit: 50,
});
await platform.waitForJob(jobId, {
onStatus: (s) => console.log(`${s.status} ${s.completed}/${s.total}`),
});
for await (const result of platform.jobResults(jobId)) {
if (result.status === "ok") {
console.log(result.url, result.markdown.length);
} else {
console.warn(result.url, result.error.code);
}
}Lower-level pieces are also exposed: scrapeAsync (a single URL as an async job),
getJob(jobId) and getJobResults(jobId, { cursor }) for manual polling and paging.
Error handling
Every non-2xx response throws a PlatformApiError carrying the API's error code, the HTTP
status, and retryAfter (seconds) when the server provided one. The client never retries
on its own.
import { PlatformApiError } from "@webforai/platform";
try {
await platform.scrape({ url });
} catch (error) {
if (error instanceof PlatformApiError) {
switch (error.code) {
case "payment_required": // out of credits
case "rate_limited": // back off; error.retryAfter may be set
case "engine_unavailable": // deployment lacks that engine's config
}
}
}The full list of codes and shapes lives in the API reference.
Custom fetch (Cloudflare Workers and other special runtimes)
All I/O goes through a single injectable fetch. By default the client binds the global
fetch, but you can supply your own — the expected type (FetchLike) is structural, so it
does not depend on lib.dom, @types/node or @cloudflare/workers-types agreeing about what
fetch is:
import { createPlatformClient, type FetchLike } from "@webforai/platform";
// Cloudflare Workers: route the client through a service binding
const platform = createPlatformClient({
apiKey: env.WEBFORAI_API_KEY,
fetch: (url, init) => env.PLATFORM.fetch(url, init),
});
// Or wrap for instrumentation / retries / a corporate proxy (undici, node-fetch, …)
const logged: FetchLike = async (url, init) => {
console.log(init.method, url);
return fetch(url, init);
};Anything that accepts (url: string, init: { method, headers?, body? }) and resolves to an
object with ok, status, headers.get() and json() qualifies — a plain object is fine,
no Response class required. The same injection point is what the client's own test suite
uses. On runtimes without a global fetch, constructing a client without fetch throws a
clear error instead of failing inside the first request.
Demo endpoint
The public, keyless demo endpoint is available too (rate-limited, truncated output):
const demo = await createPlatformClient().demoScrape({ url: "https://example.com" });
