Perceive, the web scraping API for agents
One render returns every output, and every read comes back scored.
Free while in beta. No card. 500 ops a month.
POST /v2/perceivecurl -X POST https://api.enconvert.com/v2/perceive \
-H "X-API-Key: sk_your_private_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.enconvert.com/blog/url-to-markdown-api",
"outputs": ["markdown", "screenshot", "pdf", "links", "structured"]
}'{
"operation_id": "per_e1005160f8c14ea1be762eceaa254d17",
"render_quality": 1.0,
"is_blocked": false,
"outputs": {
"markdown": { "size_bytes": 14332 },
"screenshot": { "size_bytes": 192177 },
"pdf": { "size_bytes": 492006 },
"links": { "size_bytes": 7309 }
},
"structured": { "metadata": { "title": "URL to Markdown API […]" } }
}Two reads, two honest verdicts
One page delivered, one refused, and your code can tell which.
"url": "https://www.enconvert.com/blog/url-to-markdown-api",
"status_code": 200,
"render_quality": 1.0,
"deductions": {},
"billed": true,
"outputs": {
"markdown": { "content_type": "text/markdown; charset=utf-8" },
"screenshot": { "content_type": "image/png" },
"pdf": { "content_type": "application/pdf" },
"links": { "content_type": "application/json" }
},
"warnings": []"url": "https://www.g2.com/products/slack/reviews",
"status_code": 403,
"render_quality": 0.0,
"deductions": { "empty_body": 0.7, "http_error": 0.7 },
"billed": false,
"outputs": {},
"warnings": [
[…]
"render quality: the page was scored as not delivered (deductions: empty_body, http_error); no artifacts were produced and this read is not billed."
]Real output, captured 2026-11-11. […] marks a cut.
What you send and get
| Field | What it does |
|---|---|
outputs | Any mix of Markdown, cleaned or raw HTML, a screenshot, a full-page screenshot, a PDF, links, images and structured data, from one render. |
wait_for | A CSS selector or a JavaScript expression to await before capture, for pages that load late. |
render_quality | From 0 to 1 on every read. Under 0.40 is a failed render. |
deductions | The named reasons a score dropped, such as anti_bot_challenge, login_wall, soft_404 or unhydrated_shell. Eleven in all. |
billed | One op per URL. A read that did not deliver the page is not billed. |
What one render replaces
Three separate calls
The Markdown, the screenshot and the PDF come from the same render.
A browser fleet
A fast fetch goes first, then headless Chrome when the page needs JavaScript.
Your own block detector
Eleven named deductions say why a read failed.

Your first scored read
Get an API key
X-API-Key: sk_your_private_keyRender a page
POST /v2/perceiveBranch on the score
render_quality >= 0.40
More of the same API
- URL to Markdown APIAny URL as clean Markdown, with a score.
Perceive POST /v2/perceive - RAG Ingestion APIA site or a set of files in, one chunked JSONL out.
Ingest POST /v2/ingest - Structured Data Extraction APIYour schema in, exactly that JSON out.
Distill POST /v2/distill - Web Search APIA query in, ranked results and their pages out.
Lookup POST /v2/lookup - Website Change Monitoring APIWatch a URL and get a webhook when it changes.
Watch POST /v2/watch
Questions about the scraping API
What does the web scraping API return?
POST /v2/perceive renders a URL once in a real headless browser and returns the outputs you ask for: Markdown, cleaned or raw HTML, a screenshot, a PDF, the links, the images and structured data. Files come back as pre-signed download URLs that last 15 minutes, and structured data comes back inline.
Does it render JavaScript pages?
Yes. A read tries a fast fetch with a real Chrome TLS fingerprint first and moves to a headless Chrome render when the page is blocked or needs JavaScript. In the browser, cookie banners are dismissed and the page is scrolled to load lazy content. You can also wait on a CSS selector or run your own JavaScript before capture.
What happens when a site blocks the scraper?
The read is reported, never disguised as content. A challenge page comes back with is_blocked true, and an error status or a login wall shows in deductions. Either way the response is a 200 with a low render_quality, an empty outputs object and billed false.
Can I scrape many URLs in one request?
Yes. POST /v2/perceive/batch takes up to 1,000 URLs that share one set of options. Batches over 10 URLs run asynchronously behind a job_id you poll.
Does a cached result cost an op?
Yes. Results are cached for one hour, and a cache hit still bills one op, because the allowance meters operations, not browser renders. Set cache_mode to bypass or refresh to render fresh.
What does it cost?
One op per URL read. The free Founding plan includes 500 ops a month with no credit card, and paid plans start at $29 a month.