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/perceive
curl -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"]
  }'

200 OKTrimmed

{
  "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.

A page that delivered
"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": []
A page that refused the read
"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

FieldWhat it does
outputsAny mix of Markdown, cleaned or raw HTML, a screenshot, a full-page screenshot, a PDF, links, images and structured data, from one render.
wait_forA CSS selector or a JavaScript expression to await before capture, for pages that load late.
render_qualityFrom 0 to 1 on every read. Under 0.40 is a failed render.
deductionsThe named reasons a score dropped, such as anti_bot_challenge, login_wall, soft_404 or unhydrated_shell. Eleven in all.
billedOne 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.

Left: an empty open box on a kitchen scale, the needle at its low end. Right: a full box on the same scale, the needle at its high end.

Your first scored read

  1. Get an API key

    X-API-Key: sk_your_private_key
  2. Render a page

    POST /v2/perceive
  3. Branch on the score

    render_quality >= 0.40

More of the same API

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.

Read a page you can trust