PageVistly API
Screenshots, PDFs, and OG images — rendered in real Chrome, returned as agent-friendly content.
Base URL: https://api.pagevistly.com
Quick start
Grab an API key from the dashboard (Google sign-in), then call any endpoint.
# Screenshot a URL curl -X POST https://api.pagevistly.com/v1/screenshot \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{"url":"https://stripe.com/pricing","full_page":true}' \ --output page.png # Agent mode — compact preview + text + signed URL curl -X POST https://api.pagevistly.com/v1/screenshot \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{"url":"https://stripe.com/pricing","response":"agent","detail":"low"}'
const res = await fetch("https://api.pagevistly.com/v1/screenshot", { method: "POST", headers: { "Authorization": "Bearer sk_live_xxx", "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://example.com", response: "agent" }), }); const data = await res.json();
Authentication
All /v1/* endpoints require an API key. Pass it as a Bearer token, or the api_key query/body parameter.
Authorization: Bearer sk_live_xxx
# or
?api_key=sk_live_xxx
Keys are created in the dashboard. Each key has its own rate limit and monthly quota tied to your plan.
Screenshot POST /v1/screenshot
Capture a webpage as PNG/JPEG/WebP. Add response: "agent" to get context-friendly JSON instead of binary image data. Supports GET with query params too.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | — | Required. URL to capture (http/https only) |
format | string | png | png / jpeg / webp |
full_page | bool | false | Capture full scrollable height |
width | int | 1280 | Viewport width (320–3840) |
height | int | 800 | Viewport height (240–2160) |
device_scale | int | 1 | Retina factor (1–3) |
wait_until | string | load | load / networkidle |
wait_for_selector | string | — | CSS selector to wait for before capture |
delay_ms | int | 0 | Extra wait after load (0–10000) |
block_ads | bool | false | Strip ads and trackers before capture |
block_cookie_banners | bool | false | Hide cookie consent walls |
cache_ttl | int | 0 | Cache response for N seconds (0–604800) |
response | string | image | image = binary; agent = JSON |
detail | string | low | low = preview + text; high = text only, no image |
max_tokens | int | 4000 | Token budget for text (256–32000) |
diff | bool | false | Compare with last render, return changed + text diff |
visual | bool | false | Describe screenshot with a vision model (charts/Canvas) |
Agent mode
When response: "agent", the API returns JSON instead of binary. This is the core of PageVistly — output designed for agent context, not human eyeballs.
{
"ok": true,
"title": "Stripe Pricing",
"text": "Pricing. Starter $9...",
"links": [{ "href": "...", "text": "..." }],
"preview": "iVBOR...",
"est_tokens": 1800,
"full_res_url": "https://api.pagevistly.com/v1/file?key=..."
}
| Field | Description |
|---|---|
title | Page title |
text | Extracted page text (DOM, not OCR — more accurate and free) |
links | Up to 50 links with anchor text |
preview | Downscaled JPEG preview (~30KB), only when detail=low |
est_tokens | Estimated token cost of this response |
full_res_url | Signed URL to fetch the full-res original on demand |
text; multimodal models also get preview. Both skip the 500KB original unless they explicitly fetch full_res_url.Visual diff
Add diff: true to detect what changed since the last render of the same URL — ideal for monitoring agents.
{
"changed": true,
"diff": {
"added": ["Pro $39/mo"],
"removed": ["Pro $29/mo"]
}
}
The first call with diff: true establishes a baseline (no changed field). Subsequent calls compare against it. Baseline text is stored in R2 keyed by URL.
Visual description
Add visual: true to describe the screenshot with a vision model — useful for charts, tables, or Canvas content that DOM text extraction can't capture.
{
"visual_description": "A pricing page with three tiers: Free, Starter ($5/mo), Pro ($15/mo)..."
}
PDF POST /v1/pdf
Convert a URL or raw HTML to a print-perfect PDF.
| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | — | URL to convert (use url or html, not both) |
html | string | — | Raw HTML (max 2MB) |
format | string | A4 | A4 / Letter / Legal |
landscape | bool | false | Landscape orientation |
print_background | bool | true | Print CSS backgrounds |
margin_mm | int | 10 | Page margin in mm (0–50) |
page_ranges | string | — | e.g. "1-3" |
curl -X POST https://api.pagevistly.com/v1/pdf \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"A4"}' \ --output page.pdf
OG image POST /v1/og-image
Generate a 1200×630 social card from a template.
| Parameter | Type | Default | Description |
|---|---|---|---|
template | string | default | blog / product / default |
title | string | — | Required. Card title (max 200) |
subtitle | string | — | Card subtitle (max 300) |
author | string | — | Author / brand name (max 80) |
Fetch full-res GET /v1/file
Retrieve the full-resolution original behind a full_res_url from agent mode.
curl "https://api.pagevistly.com/v1/file?key=screenshot/<hash>.png" \ -H "Authorization: Bearer sk_live_xxx" --output full.png
MCP server
Drop PageVistly straight into Claude Desktop, Cursor, or any MCP client. Two tools: read_page and make_og_image.
{
"mcpServers": {
"pagevistly": {
"command": "npx",
"args": ["-y", "@pagevistly/mcp"],
"env": { "PAGEVISTLY_API_KEY": "sk_live_xxx" }
}
}
}
| Tool | Description |
|---|---|
read_page | Read a webpage as agent-friendly content (preview + text + signed URL). Args: url, detail, max_tokens |
make_og_image | Generate an OG social card. Args: template, title, subtitle, author |
Caching
PageVistly uses a three-tier cache: edge Cache API → R2 persistent cache → live render.
| Header | Meaning |
|---|---|
X-Cache: HIT-EDGE | Served from edge cache (milliseconds) |
X-Cache: HIT-R2 | Served from persistent storage |
X-Cache: MISS | Fresh render |
Enable caching with cache_ttl (seconds). Cached responses don't count against your quota.
Error codes
Errors are deterministic JSON — agents can parse them reliably. Format: {"error": "<code>"}.
| Code | HTTP | Meaning |
|---|---|---|
missing_api_key | 401 | No API key provided |
invalid_api_key | 401 | Key not found |
api_key_disabled | 401 | Key is inactive |
rate_limit_exceeded | 429 | Per-minute limit hit |
quota_exceeded | 429 | Monthly quota exhausted |
invalid_url | 400 | Malformed URL |
url_protocol_not_allowed | 400 | Not http/https |
url_host_not_allowed | 400 | Private/internal/reserved address (SSRF blocked) |
invalid_json | 400 | Malformed request body |
missing_input / ambiguous_input | 400 | PDF: need url or html, not both |
html_too_large | 400 | PDF html exceeds 2MB |
missing_title | 400 | OG image: title required |
render_failed | 502 | Render error (page timeout, browser failure) |
not_found | 404 | Full-res object not found |
Rate limits & quota
Rate limit: 60 requests/minute per key. Monthly quota by plan:
| Plan | Monthly quota |
|---|---|
| Free | 500 renders |
| Starter | 2,000 renders |
| Pro | 10,000 renders |
Usage resets on the 1st of each month (UTC). Track usage in the dashboard.