Web Unlocker API: Render Any Page to HTML, Markdown, or PNG
Advanced Data Extraction Specialist
TL;DR:
- The Web Unlocker turns any URL into clean data with one POST. Send a URL to
unlocker.webunlocker; get back the page as HTML, plaintext, Markdown, a screenshot, or extracted content β no browser to manage. - JavaScript rendering is a flag, not a separate product. Set
jsRender.enabled: trueand the page is rendered in a real browser before the response is built, so client-side content is already there. - You pick the shape of the response.
response.typeis one ofhtml,plaintext,markdown,png,jpeg,network, orcontentβ request Markdown for LLMs, PNG for a screenshot,contentfor structured extraction. - Proxy country is a field. Set
proxy.countryto route the request through residential egress in that region; a mismatched region is a common reason a page renders differently. - Two timeouts govern every call. A 30-second page-load ceiling and a 180-second global execution ceiling β the page-load limit takes priority.
- Free to start. New Scrapeless accounts include free Universal Scraping API usage β sign up at app.scrapeless.com.
Introduction: one endpoint, any page, the shape you asked for
Most scraping code spends its effort on everything around the data: launching a browser, waiting for JavaScript, handling the block, then parsing HTML into something usable. The Scrapeless Universal Scraping API collapses that into a single HTTP request. You POST a URL to the Web Unlocker actor, and the response is the page β already rendered, already in the format you asked for.
This guide walks through the unlocker.webunlocker actor end to end: the request shape, a first curl, the response envelope, a Python integration, the seven response types JavaScript rendering can return, and how to keep requests clean. Every request and response below was captured against the live API.
What you can do with it
- Fetch a page as raw HTML β a plain GET through clean egress, for when you'll parse the markup yourself.
- Render JavaScript-heavy pages β set
jsRender.enabledand read content that only exists after the client runs. - Get Markdown for an LLM β request
type: markdownand feed the result straight into a RAG pipeline or prompt. - Capture a screenshot β request
type: pngorjpegand get the rendered viewport as an image. - Extract structured content β request
type: contentto pull headings, links, tables, emails, images, and metadata out of the page. - Watch network responses β request
type: networkto capture the XHR/fetch responses a page makes, filtered by URL, status, and method. - Drive the page first β run
instructions(wait for a selector, click, fill, press keys) before the response is built.
Why Scrapeless Universal Scraping API
The Universal Scraping API is the managed web-unlocking surface: you send a URL, it handles rendering, egress, and anti-detection, and returns clean data. For this workflow specifically, it brings:
- Cloud-side JavaScript rendering β a real browser runs the page, so single-page apps and lazy-loaded content resolve before the response is built.
- Residential proxies in 195+ countries β route through
proxy.countryso the exit IP reputation reads clean and geo-routed pages serve correctly. - Automatic challenge handling β reCAPTCHA v2, Cloudflare Turnstile, and the Cloudflare interstitial are handled inside the actor.
- Seven response formats β HTML, plaintext, Markdown, PNG, JPEG, network capture, and structured content from the same endpoint.
- A single HTTP contract β no browser lifecycle, no driver versions; the response is the data.
Get your API key on the free plan at app.scrapeless.com.
Prerequisites
- A Scrapeless account and API key β sign up at app.scrapeless.com
curlfor the first request, and Python 3.10+ (or Node.js 18+) for the integration- Basic familiarity with HTTP and JSON
How the Web Unlocker works
Every call is a POST to one endpoint with a JSON body of {actor, input, proxy}.
Request parameters
| Field | Where | Meaning |
|---|---|---|
actor |
top level | unlocker.webunlocker |
input.url |
input | the page to fetch |
input.method |
input | HTTP method (default GET) |
input.redirect |
input | follow redirects (true/false) |
input.jsRender |
input | { enabled, response, instructions, block } β render options |
proxy.country |
proxy | ISO country code or ANY |
Auth is the x-api-token header. The response envelope is always { "code": 200, "data": ... }.
Quick capture with curl
Fetch a page as HTML through residential egress:
bash
curl -X POST https://api.scrapeless.com/api/v2/unlocker/request \
-H "x-api-token: ${SCRAPELESS_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"actor": "unlocker.webunlocker",
"input": { "url": "https://www.example.com", "method": "GET", "redirect": false },
"proxy": { "country": "ANY" }
}'
Response envelope
json
// illustrative sample β envelope shape is exact; the live GET above returned code 200 with 559 bytes of example.com HTML
{
"code": 200,
"data": "<!doctype html><html>β¦</html>"
}
A code of 200 means the request succeeded; data carries the payload β HTML text here, Markdown or a base64 image for other response types.
Integrating the API in Python
The same call from Python, reading the key from the environment:
python
import os
import requests
API_KEY = os.environ["SCRAPELESS_API_KEY"]
resp = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
headers={"x-api-token": API_KEY, "Content-Type": "application/json"},
json={
"actor": "unlocker.webunlocker",
"input": {"url": "https://www.example.com", "method": "GET", "redirect": False},
"proxy": {"country": "ANY"},
},
timeout=70,
)
data = resp.json()
if data.get("code") == 200:
html = data["data"]
print(len(html), "bytes of HTML")
Get your API key on the free plan: app.scrapeless.com
Rendering JavaScript: the seven response types
To render the page in a real browser first, add jsRender. The response.type decides what comes back. Request Markdown for a page β ideal for feeding an LLM:
python
payload = {
"actor": "unlocker.webunlocker",
"proxy": {"country": "ANY"},
"input": {
"url": "https://www.example.com",
"jsRender": {
"enabled": True,
"response": {"type": "markdown"},
},
},
}
resp = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
json=payload,
headers={"x-api-token": API_KEY, "Content-Type": "application/json"},
timeout=70,
)
print(resp.json()["data"])
# "# Example Domain\n\nThis domain is for use in documentation examples..."
The type field selects the format:
response.type |
Returns |
|---|---|
html |
rendered HTML after JavaScript runs |
plaintext |
visible text, markup stripped |
markdown |
the page as Markdown (LLM-ready) |
png / jpeg |
a screenshot as a base64 string |
network |
captured XHR/fetch responses, filtered by urls, status, methods |
content |
structured extraction β headings, links, tables, images, emails, metadata |
For a screenshot, request png and decode the base64 data to bytes:
python
import base64
payload["input"]["jsRender"]["response"] = {"type": "png"}
resp = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
json=payload,
headers={"x-api-token": API_KEY, "Content-Type": "application/json"},
timeout=70,
)
with open("page.png", "wb") as f:
f.write(base64.b64decode(resp.json()["data"]))
Driving the page before capture
When content appears only after interaction, pass instructions β each is a verb the renderer runs in order before building the response:
json
{
"actor": "unlocker.webunlocker",
"input": {
"url": "https://example.com",
"jsRender": {
"enabled": true,
"instructions": [
{ "waitFor": [".dynamic-content", 30000] },
{ "click": ["#load-more", 1000] },
{ "fill": ["#search-input", "search term"] },
{ "keyboard": ["press", "Enter"] },
{ "evaluate": "window.scrollTo(0, document.body.scrollHeight)" }
]
}
}
}
You can also cut bandwidth by blocking resource types you don't need with jsRender.block.resources (for example Image, Font, Media, Stylesheet), which the fetch layer skips per the resource categories defined in the Fetch API.
How to avoid common problems
- A field that isn't on the page is null, not an error. Treat every extracted field as optional and guard for its absence rather than assuming it's present.
- Mind the two timeouts. A page-load ceiling of 30 seconds and a global execution ceiling of 180 seconds bound every call, and the page-load limit takes priority β keep
waitForvalues inside that budget. The HTTP semantics specification defines the status codes you'll see if a target itself errors. - Pin the country to the content. If a page geo-routes, set
proxy.countryto the region that serves the version you want;ANYis fine when it doesn't. - Choose the response type deliberately. Request
markdownorcontentwhen you want data, nothtmlyou'll have to parse β the extraction happens server-side either way, and the automated-traffic patterns the unlocker handles are catalogued in the OWASP Automated Threats project.
Conclusion: the page, in the shape you need
The Web Unlocker reduces a scrape to one decision: which URL, and which response type. Rendering, egress, and anti-detection are handled inside the actor, so a JavaScript-heavy page becomes clean Markdown or a screenshot in a single request. Pair it with the Scraping Browser when you need a full interactive session, and read up on residential versus datacenter egress since proxy reputation decides most render outcomes. The Universal Scraping API docs cover every field.
Ready to Build Your AI-Powered Data Pipeline?
Join our community to claim a free plan and connect with developers building extraction pipelines: Discord Β· Telegram.
Sign up at app.scrapeless.com for free Universal Scraping API usage, and see pricing for scale.
FAQ
Q: What is the difference between the Web Unlocker and the Scraping Browser?
The Web Unlocker is a single request/response endpoint β send a URL, get the page back in one call. The Scraping Browser is a full interactive cloud browser you drive with Puppeteer or Playwright. Use the unlocker for fetch-and-parse; use the browser for multi-step sessions.
Q: Do I need to enable JavaScript rendering?
Only when the content you need is client-rendered. A plain GET returns the server HTML; adding jsRender.enabled: true runs the page in a real browser first, which is what you want for single-page apps and lazy-loaded content.
Q: Which response type should I use for an LLM pipeline?
markdown β it returns the page as clean Markdown with markup stripped, which is what most RAG and prompt pipelines want. Use content when you need discrete fields (headings, links, tables) instead of prose.
Q: How do I get a screenshot?
Set response.type to png or jpeg; the data field comes back as a base64 string you decode to image bytes.
Q: Do I need a proxy?
Egress is built in. Set proxy.country to route through residential IPs in a specific region, or ANY to let the service choose. Pinning a country matters when a page geo-routes or challenges datacenter IPs.
Q: What are the timeouts?
A fixed 30-second page-load ceiling and a 180-second global execution ceiling. The page-load limit takes priority and can end the call before the global limit, so keep any waitFor values within that budget.
At Scrapeless, we only access publicly available data while strictly complying with applicable laws, regulations, and website privacy policies. The content in this blog is for demonstration purposes only and does not involve any illegal or infringing activities. We make no guarantees and disclaim all liability for the use of information from this blog or third-party links. Before engaging in any scraping activities, consult your legal advisor and review the target website's terms of service or obtain the necessary permissions.



