Skip to content

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/platform

Scrape

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;   // 5

Self-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" });