Turn a whole site
into clean data.
Point Writ at a URL. It maps the site, spreads the crawl across your whole fleet (best-first, in parallel), and hands every page back as clean Markdown or structured JSON. Scrape one page or crawl a million.
One page in, clean Markdown out.
Writ strips the chrome (nav, cookie banners, ads, boilerplate) and keeps the content, so an LLM reads the page, not the layout. Press Scrape:
Every discoverable URL, in seconds.
Map merges the sitemap with links Writ discovers live, so you see the whole surface before you spend a single page-crawl. Pick your seeds, then crawl only what matters.
One crawl, your whole fleet.
A crawl isn't one machine grinding a queue. Writ shares one frontier across every agent (best-first, so the most relevant pages come back first) and scales linearly as you add workers. Add agents:
The frontier depends on who's signed in.
A public crawler dead-ends at the login wall. Attach a persona and the same seed URL opens onto the pages only your account can see - order history, invoices, member pages that appear in no sitemap - and the map itself grows. Toggle the session:
Without a session the frontier dead-ends at /login. Everything past it is undiscoverable — not slow to reach, undiscoverable: those URLs appear in no sitemap and nothing links to them from the public site.
Illustrative topology. A real Harvest runs inside the scope you set — includes, excludes and rate caps — and respects robots.txt.
Crawl what you mean, not everything.
Describe the intent and Writ scores every URL against it, following the ones that match and skipping the rest. You pay for the pages you wanted, not the whole site. Pick a target:
HTML, JS, PDFs, images: all readable.
Writ routes each resource to the cheapest lane that works: direct HTTP for static pages, a real browser for JS apps, doc-extract for PDFs and office files, offline OCR for images. You get text either way.
Markdown, JSON, or a searchable dataset.
Take clean Markdown, or hand Writ a schema and get structured JSON per page. Every crawl lands in a dataset you can query, with no re-crawl to answer the next question.
A twentieth of a cent per page. Free to start.
Crawls run on the Writ fleet — a free account starts you off, no card required. Pages are billed from your pool, with browser and OCR lanes weighted higher since they cost more to run. Self-host the open-core and your own agents crawl at no charge from us. Drag your monthly pages:
Kick off a crawl in one call.
Start a crawl, poll its status, stream results over REST, an MCP tool, or the SDK. Pick one:
A whole-site crawl is one POST with a key. Writ fans the seed across the fleet and lands the result as a callable dataset.
# No account, no key — a few same-domain pages, one level deep.
curl -X POST https://api.usewrit.app/v1/keyless/crawl \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "limit": 3}'
# With a key: the whole site, as a job you poll.
curl -X POST https://api.usewrit.app/api/crawl \
-H "Authorization: Bearer $WRIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "max_depth": 3, "page_budget": 500}'
curl https://api.usewrit.app/api/crawl/8125 -H "Authorization: Bearer $WRIT_API_KEY" No account needed to try it: the keyless tier crawls a few same-domain pages, one level deep, against a daily allowance. Whole-site scope, logins and a callable dataset need a key.
Point your assistant at Writ and it can crawl a site directly, then read the rows back without you writing a parser.
claude mcp add writ-cloud \
-e WRIT_API_KEY=wt_your_api_key \
-- npx -y writ-mcp
# then, one line in the session:
writ_crawl_site({ url: "https://example.com",
intent: "product pages with specs",
page_budget: 500, save_as: "example-catalog" })
writ_crawl_status({ crawl_id: 8125 }) Config-file clients — Claude Desktop · Cursor · Windsurf
Pass save_as and the crawl becomes re-runnable and callable, so later runs can reuse recent data instead of re-crawling.
npm install github:usewrit/writ-sdks --workspace typescript
import { CloudApi } from "@usewrit/agent-sdk";
const cloud = new CloudApi({ apiKey: process.env.WRIT_API_KEY });
const job = await cloud.crawl({ url: "https://example.com", max_depth: 3, page_budget: 500 });
const status = await cloud.crawlStatus(job.id);
console.log(status.pages_done, "/", status.pages_discovered); pip install "git+https://github.com/usewrit/writ-sdks#subdirectory=python"
from writ_agent import Cloud
cloud = Cloud(api_key=os.environ["WRIT_API_KEY"])
job = cloud.crawl("https://example.com", max_depth=3, page_budget=500)
status = cloud.crawl_status(job["id"])
print(status["pages_done"], "/", status["pages_discovered"]) go get github.com/usewrit/writ-sdks/go
client := writ.New(writ.WithAPIKey(os.Getenv("WRIT_API_KEY")))
job, err := client.Cloud.Crawl(ctx, writ.CrawlStartParams{
URL: "https://example.com",
MaxDepth: writ.Ptr(int64(3)),
PageBudget: writ.Ptr(int64(500)),
})
status, _ := client.Cloud.CrawlStatus(ctx, job.ID) cargo add writ-client --git https://github.com/usewrit/writ-sdks
use writ_client::{CloudClient, CrawlStartParams};
let cloud = CloudClient::from_env()?;
let job = cloud.crawl(&CrawlStartParams {
url: "https://example.com".into(),
max_depth: Some(3),
page_budget: Some(500),
..Default::default()
}).await?;
let status = cloud.crawl_status(job.id).await?;