V2 Competitive Hardening #
Summary
A round of V2 improvements closing the biggest gaps against dedicated web-data APIs: durable batch perception that survives restarts, high-value search enrichment (concurrent multi-format rendering + a synthesized cited answer + structured extraction across results), prompt-only structured extraction, more capable site discovery (gzipped sitemaps, WAF-resilient fetches, a far higher crawl ceiling), and a security-hardening pass (DNS-rebind protection on the render path, an opt-in local URL threat policy, and CSP respected by default).
/v2/perceive/batch — durable, resumable batches + cancel
Batches are now restart-safe. Previously the batch envelope (the shared render options and output mode) lived only in memory, so a server restart mid-batch failed the whole batch and you had to resubmit. The envelope is now persisted, so an interrupted batch resumes automatically and re-renders only the URLs that had not finished — already-completed URLs keep their artifacts.
- New:
DELETE /v2/perceive/batch/{job_id}— cancel a running batch. The worker stops between URLs; already-completed URLs remain available. Idempotent; a terminal batch is unchanged. Project-scoped404. - New batch status:
canceled(joinsqueued|processing|completed|partial|failed). - The batch status endpoint now reports the true
output_modeand thecanceledstate from the durable batch record.
No request-shape change for submitting a batch — existing calls keep working.
/v2/lookup — high-value enrichment (enrich)
A new optional enrich object turns "search, then read the top results" from a slow, markdown-only, one-at-a-time pass into a fast, configurable one — and can synthesize a grounded answer across the results.
enrich fields:
- outputs[] — which perceive outputs to produce per enriched result (e.g. markdown, html_cleaned, links, screenshot, structured). Defaults to ["markdown"].
- concurrency (1–5, default 3) — how many result URLs to enrich in parallel. Markdown/HTML renders use the no-browser path and truly parallelize; screenshot/PDF renders serialize on the shared browser.
- schema (aliased) — run schema-driven structured extraction against each enriched result; the data appears under each result's perceive.structured.
- synthesize_answer (default false) — synthesize one cited, grounded answer to the query across the enriched results, returned on the response as answer (with answer_sources). Uses the perceived page content when available, otherwise result snippets.
- answer_prompt — an optional question to answer instead of the raw query.
New response fields: answer, answer_sources[]. When enrich is omitted, perceive_top keeps its previous behavior (markdown-only, sequential). Structured extraction and answer synthesis require an LLM-enabled plan; on plans without it they degrade to a warning.
/v2/distill — prompt-only extraction
You can now distill without writing a schema. Supply a natural-language prompt and the extraction schema is synthesized from it (single model), then the normal two-pass CSS→LLM engine runs.
schemais now optional; provide eitherschemaorprompt(schema wins if both are given).- New response field
synthesized_schemaechoes the fields that were derived from the prompt. - Prompt-only mode requires an LLM-enabled plan; without one it returns a clear warning.
/v2/discover — gzipped sitemaps, WAF resilience, higher ceiling
- Gzipped sitemaps (
sitemap.xml.gzand anyContent-Type: application/gzipsitemap) are now decompressed and parsed instead of silently failing. - WAF resilience: sitemap and robots fetches now send realistic browser headers, so sites that 403/503 a bare client are far more likely to return their sitemap.
- Higher crawl ceiling: crawl-mode HTTP fetches are no longer capped at 50. The ceiling now equals your
max_urls(up to 1000), so large sites are enumerated far more completely. Operators can lower it withDISCOVER_CRAWL_MAX_PAGESif needed. This also lifts the same ceiling for/v2/ingestcrawl-mode jobs.
Security hardening
- DNS-rebind / redirect protection on the render path: every request the browser makes (the initial navigation, redirects, and subresources) is re-validated against the SSRF rules at request time — not only the seed URL before navigation — so a hostname that rebinds to a private address, or a redirect to an internal one, is blocked before the browser connects. The no-browser TLS engine additionally pins each connection to the validated IP.
- Local URL threat policy (opt-in, no external dependency): a configurable denylist of domains, TLDs, and host patterns enforced on every fetched URL, with an audit trail of blocks. Configure via
THREAT_BLOCKED_DOMAINS,THREAT_BLOCKED_TLDS, or aTHREAT_POLICY_FILE. Empty by default (blocks nothing). - Content-Security-Policy respected by default for the raw browser-context path (previously bypassed). Callers that need CSP bypass for scripted DOM interaction opt in explicitly.
These changes require no API changes on your side; blocked URLs return 400.