
Research
/Security News
737 Chrome VPN Extensions Linked to Brand Impersonation and Browser Traffic Redirection
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.
@webclaw/sdk
Advanced tools
TypeScript SDK for the Webclaw web extraction API
npm install @webclaw/sdk
pnpm add @webclaw/sdk
yarn add @webclaw/sdk
bun add @webclaw/sdk
import { Webclaw } from "@webclaw/sdk";
const client = new Webclaw({ apiKey: "wc-YOUR_API_KEY" });
const result = await client.scrape({ url: "https://example.com", formats: ["markdown"] });
console.log(result.markdown);
Extract content from a single URL. Supports multiple output formats, CSS selectors for targeting specific elements, and cache control.
const result = await client.scrape({
url: "https://example.com",
formats: ["markdown", "text", "llm", "json"],
include_selectors: ["article", ".content"],
exclude_selectors: ["nav", "footer"],
only_main_content: true,
no_cache: true,
});
result.url // string
result.markdown // string | undefined
result.text // string | undefined
result.llm // string | undefined
result.json // unknown | undefined
result.metadata // { title?, description?, language?, ... }
result.cache // { status: "hit" | "miss" | "bypass" }
result.warning // string | undefined
28 site-specific extractors that return typed JSON (GitHub, Reddit, Amazon, YouTube, PyPI, HuggingFace, Trustpilot, etc.) instead of generic markdown. See the catalog for the full list.
// Discover available extractors
const catalog = await client.listExtractors();
catalog.extractors.forEach((e) => console.log(e.name, "-", e.label));
// Run a specific extractor
const pr = await client.scrapeVertical(
"github_pr",
"https://github.com/rust-lang/rust/pull/123456",
);
console.log(pr.data); // { title, state, author, commits, reviews, ... }
// Amazon product as typed JSON
const product = await client.scrapeVertical(
"amazon_product",
"https://www.amazon.com/dp/B0C6KKQ7ND",
);
console.log(product.data.price, product.data.rating);
The data field is extractor-specific; call listExtractors() to discover what each returns.
Web search with optional parallel scraping of each result page.
const result = await client.search({
query: "web scraping tools 2026",
num_results: 10,
scrape: true,
formats: ["markdown"],
country: "us",
lang: "en",
topic: "technology",
});
for (const r of result.results) {
console.log(r.title, r.url, r.snippet);
console.log(r.markdown); // present when scrape: true
}
Discover URLs from a site's sitemap.
const result = await client.map({ url: "https://example.com" });
console.log(`Found ${result.count} URLs`);
result.urls.forEach((url) => console.log(url));
Discover API endpoints embedded in a page's JavaScript — scans inline <script> bodies plus <script src> bundles for request paths, absolute URLs, GraphQL, and WebSocket endpoints. This surfaces the request layer that map (sitemap-based) can't see.
const result = await client.endpoints({
url: "https://example.com",
include_third_party: false, // default; set true to include other hosts
max_bundles: 20, // default & max; bundles fetched on top of inline JS
});
console.log(`${result.endpoint_count} endpoints across ${result.bundles_scanned} bundles`);
for (const ep of result.endpoints) {
console.log(ep.kind, ep.value, ep.first_party ? "(1st-party)" : "(3rd-party)");
}
result.hosts // distinct hosts seen, e.g. ["api.example.com"]
result.truncated // true if results were capped by max_bundles
Security:
endpoints,hosts, and their fields are extracted from page content (inline scripts and fetched bundles), which is attacker-influenced. The SDK does not sanitize them. Never feed a returnedvalueorsourceinto another request, shell command,eval, or SQL query without your own validation.
Scrape multiple URLs in parallel with configurable concurrency.
const result = await client.batch({
urls: ["https://a.com", "https://b.com", "https://c.com"],
formats: ["markdown"],
concurrency: 5,
});
for (const item of result.results) {
if ("error" in item) console.error(item.url, item.error);
else console.log(item.url, item.markdown?.length);
}
LLM-powered structured data extraction. Provide a JSON schema for typed output, or a natural-language prompt for flexible extraction.
// Schema-based extraction
const result = await client.extract({
url: "https://example.com/pricing",
schema: {
type: "object",
properties: {
plans: { type: "array", items: { type: "object" } },
},
},
});
console.log(result.data);
// Prompt-based extraction
const result2 = await client.extract({
url: "https://example.com",
prompt: "Extract all pricing tiers with names and prices",
});
console.log(result2.data);
Turn a company URL into a structured lead — name, summary, socials, tech stack, pricing, and contact emails, plus people: founders and team members, each with their LinkedIn and X links where found. The people list is assembled via web search.
Pricing: a flat 100 credits per successful lead.
const result = await client.lead("https://resend.com");
result.url // "https://resend.com"
result.domain // "resend.com"
result.lead.company_name // "Resend"
result.lead.summary // "Email API for developers."
result.lead.socials // { linkedin?, x?, github? }
result.lead.tech // ["Next.js", "React", "AWS"]
result.lead.pricing // [{ plan: "Free", price: "$0" }, ...]
result.lead.emails // [{ type: "support", email: "support@resend.com" }, ...]
result.lead.people // [{ name: "Zeno Rocha", role: "CEO", linkedin: "…", x: "…" }, ...]
result.people_source // "web_search"
result.cache // "hit" | "miss"
result.credits // 100
Every field on result.lead is optional — the extractor fills in whatever it can find. Each person's linkedin and x are null when none was found. Pass { no_cache: true } to force a fresh crawl.
Generate a concise summary of a page's content.
const result = await client.summarize({
url: "https://example.com/blog/long-article",
max_sentences: 3,
});
console.log(result.summary);
Detect content changes on a page. Optionally provide a previous state to diff against.
const result = await client.diff({
url: "https://example.com",
previous: { title: "Old Title", body: "Old content..." },
});
console.log(result.changes);
Extract brand identity information (name, colors, fonts, logos) from a URL.
const result = await client.brand({ url: "https://example.com" });
console.log(result); // { name, colors, fonts, logos, ... }
Start an async deep research job. The SDK automatically polls until the job completes (up to 20 minutes by default; override with maxWait).
Note: every research job now runs in deep mode. The
deeprequest flag is deprecated and ignored by the API — don't pass it.
const result = await client.research(
{
query: "How do modern web crawlers handle JavaScript rendering?",
max_sources: 15,
},
{ interval: 3_000 },
);
console.log(result.report);
console.log("Sources:", result.sources?.length);
console.log("Findings:", result.findings?.length);
You can also poll manually using getResearchStatus:
const job = await client.research({ query: "AI trends 2026" });
// ... or check status independently:
const status = await client.getResearchStatus(job.id);
Start an async crawl job that discovers and scrapes pages from a root URL.
const job = await client.crawl({
url: "https://example.com",
max_depth: 3,
max_pages: 100,
use_sitemap: true,
});
console.log("Job ID:", job.id);
Poll with waitForCompletion, which resolves when the crawl finishes or fails:
const result = await job.waitForCompletion({
interval: 2_000, // polling interval in ms
maxWait: 300_000, // max wait time in ms (5 min)
});
console.log(`Status: ${result.status}`);
console.log(`${result.completed}/${result.total} pages`);
for (const page of result.pages) {
console.log(page.url, page.markdown?.length);
}
Or check status manually at any time:
const status = await job.getStatus();
// or: const status = await client.getCrawlStatus(job.id);
Monitor URLs for content changes. Create watchers, check them on demand, and receive webhook notifications when content changes.
Create a watch
const watch = await client.watchCreate({
url: "https://example.com/pricing",
name: "Pricing page",
interval_minutes: 60,
webhook_url: "https://your-server.com/webhooks/webclaw",
});
console.log("Watch ID:", watch.id);
List all watches
const watches = await client.watchList(10, 0); // limit, offset
for (const w of watches) {
console.log(w.id, w.url, w.active);
}
Get a single watch
const watch = await client.watchGet("watch_abc123");
console.log(watch.last_checked_at, watch.last_changed_at);
Trigger an immediate check
const updated = await client.watchCheck("watch_abc123");
console.log(updated.last_checked_at);
Delete a watch
await client.watchDelete("watch_abc123");
Monitor X for new tweets — profiles, search queries, lists, or replies to a tweet — and receive webhook notifications when new matches appear. The X analog of Watch. You can also export an account's followers or following.
Paid feature. These endpoints return
403(ScopeError) on free or lapsed accounts. Monitors and audience export are billed per X request at your plan rate (Starter 5, Growth 3, Pro 2, Scale 1 credits). Max 50 monitors per account.
Create a monitor
const monitor = await client.createXMonitor({
kind: "profile", // "profile" | "search" | "list" | "replies"
target: "@webclaw", // handle | search query | list id | tweet id (per kind)
name: "Webclaw mentions",
interval_minutes: 15, // default 15, clamped 2..10080
webhook_url: "https://discord.com/api/webhooks/...",
include_retweets: true, // default true
include_replies: true, // default true
include_quotes: true, // default true
min_faves: 0, // minimum likes to match
keyword: "scraping", // only match tweets containing this
lang: "en", // only match this language
});
console.log("Monitor ID:", monitor.id);
List monitors
const { monitors } = await client.listXMonitors(10, 0); // limit, offset
for (const m of monitors) {
console.log(m.id, m.kind, m.target, m.active);
}
Get a single monitor
const monitor = await client.getXMonitor("xmon_abc123");
console.log(monitor.last_checked_at, monitor.last_matched_at);
Update a monitor
const res = await client.updateXMonitor("xmon_abc123", {
name: "Renamed",
interval_minutes: 30,
webhook_url: "https://hooks.slack.com/services/...",
active: false, // pause it
});
console.log(res.success); // true
Trigger an immediate check
const res = await client.checkXMonitor("xmon_abc123");
console.log(res.status); // "checking" — runs in the background
Delete a monitor
const res = await client.deleteXMonitor("xmon_abc123");
console.log(res.success); // true
Webhook payload
When a monitor matches new tweets, webhook_url receives:
{
"event": "x.monitor.matched",
"monitor_id": "xmon_abc123",
"kind": "profile",
"target": "webclaw",
"new_count": 2,
"tweets": [
{
"id": "1790000000000000000",
"screen_name": "webclaw",
"text": "…",
"url": "https://x.com/webclaw/status/1790000000000000000",
"created_at": "2026-06-12T10:00:00Z",
"favorite_count": 42,
"retweet_count": 7,
"reply_count": 3,
"lang": "en",
"is_retweet": false,
"is_reply": false,
"is_quote": false
}
],
"checked_at": "2026-06-12T10:00:05Z"
}
Discord and Slack webhook URLs receive native embed/text formatting instead of this generic JSON.
Export an audience
Export an account's followers or following, cursor-paginated and metered per page at your plan rate (Starter 5, Growth 3, Pro 2, Scale 1 credits). To walk a full audience, call repeatedly — pass the returned user_id and next_cursor back in until next_cursor is null.
let cursor: string | null | undefined;
let userId: string | undefined;
do {
const page = await client.exportXAudience({
handle: userId ? undefined : "@webclaw", // resolved once (unbilled)
user_id: userId, // reuse to skip re-resolving
direction: "followers", // "followers" (default) | "following"
cursor: cursor ?? undefined,
max_pages: 2, // default 2, clamped 1..10
});
for (const u of page.users) {
console.log(u.screen_name, u.followers, u.description);
}
userId = page.user_id;
cursor = page.next_cursor;
} while (cursor !== null);
The API also exposes a Firecrawl-compatible surface at /v2/scrape, /v2/crawl, and /v2/search. These endpoints are not yet wrapped by this SDK (future work) — call them directly if you need Firecrawl drop-in compatibility today.
All errors extend WebclawError, so you can catch broadly or handle specific cases.
import {
WebclawError,
AuthenticationError,
NotFoundError,
RateLimitError,
TimeoutError,
} from "@webclaw/sdk";
try {
await client.scrape({ url: "https://example.com" });
} catch (err) {
if (err instanceof RateLimitError) {
console.error("Rate limited, retry after:", err.retryAfter, "s");
} else if (err instanceof AuthenticationError) {
console.error("Bad API key");
} else if (err instanceof NotFoundError) {
console.error("Resource not found");
} else if (err instanceof TimeoutError) {
console.error("Request timed out");
} else if (err instanceof WebclawError) {
console.error("API error:", err.message, err.status, err.body);
}
}
const client = new Webclaw({
apiKey: process.env.WEBCLAW_API_KEY!,
baseUrl: "https://api.webclaw.io", // default
timeout: 60_000, // ms, default 30_000
});
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | required | Your Webclaw API key |
baseUrl | string | https://api.webclaw.io | API base URL |
timeout | number | 30000 | Request timeout in milliseconds |
Full type definitions are included for every request and response. All types are exported from the package root:
import type {
ScrapeRequest,
ScrapeResponse,
CrawlRequest,
CrawlStatusResponse,
EndpointsRequest,
EndpointsResponse,
SearchRequest,
SearchResponse,
ExtractRequest,
ExtractResponse,
LeadRequest,
LeadResponse,
ResearchRequest,
ResearchResponse,
WatchCreateRequest,
WatchResponse,
CreateXMonitorRequest,
UpdateXMonitorRequest,
XMonitor,
ListXMonitorsResponse,
ExportXAudienceRequest,
ExportXAudienceResponse,
// ... and more
} from "@webclaw/sdk";
fetch.MIT
FAQs
TypeScript SDK for the Webclaw web extraction API
The npm package @webclaw/sdk receives a total of 14 weekly downloads. As such, @webclaw/sdk popularity was classified as not popular.
We found that @webclaw/sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.