API Reference

Full documentation for the ScreenshotAPI HTTP API. Base URL: http://localhost:3000

Authentication

Authentication is optional. Set API_KEYS in your environment to enable it. When enabled, every request must include a valid key.

Via header (recommended)

X-Api-Key: your-api-key

Via query param

?api_key=your-api-key
API keys are redacted from all server logs.

Error Responses

All errors return JSON with a consistent shape:

{ "error": "ERROR_CODE", "message": "Human-readable explanation" }
HTTPCodeCause
400VALIDATION_ERRORMissing or invalid parameters
400INVALID_URLURL is malformed or not http/https
401UNAUTHORIZEDMissing or invalid API key
403BLOCKEDURL targets private/loopback address (SSRF)
408TIMEOUTPage load exceeded SCREENSHOT_TIMEOUT_MS
422NAVIGATION_FAILEDBrowser could not load the page
422SELECTOR_NOT_FOUNDselector, click, or wait_for_selector not found
429RATE_LIMITEDToo many requests — see retryAfter in response
503POOL_EXHAUSTEDAll browser slots busy, retry later

Rate Limiting

Limits are applied per API key, or per IP when auth is disabled. Default: 60 requests/minute. Configure via MAX_REQUESTS_PER_MINUTE.

# 429 response body
{ "error": "RATE_LIMITED", "message": "Too many requests. Limit: 60 per minute.", "retryAfter": "23s" }

Caching

Every screenshot is cached by a SHA-256 hash of all capture parameters. Identical requests are served from cache without launching a browser. The X-Cache response header tells you whether the response was cached.

X-Cache: HIT — served from cache
X-Cache: MISS — fresh capture, now cached

Use cache=false to bypass cache. TTL is set via CACHE_TTL_SECONDS (default: 3600).

GET /screenshot

Capture a screenshot of a URL or rendered HTML. Returns a binary image, base64 JSON, or metadata JSON depending on response_type.

Source

ParameterTypeDefaultDescription
urlstringPage URL to capture (http/https only). Required unless html is provided required*
htmlstringBase64-encoded HTML to render instead of a URL optional

Viewport

ParameterTypeDefaultDescription
widthinteger1280Viewport width in px (100–3840)
heightinteger800Viewport height in px (100–2160)
device_scale_factorfloat1Device pixel ratio. Use 2 for retina
viewport_mobilebooleanfalseEnable mobile emulation (touch events, mobile meta tag)

Capture

ParameterTypeDefaultDescription
formatenumpngpng · jpeg · webp · pdf
qualityinteger801–100. Applies to jpeg and webp only
full_pagebooleanfalseCapture the full scrollable height of the page
omit_backgroundbooleanfalseTransparent background (PNG only)
selectorstringCSS selector — capture only that element
clip_x / clip_yinteger0Clip region top-left corner in px
clip_width / clip_heightinteger0Clip region size (0 = disabled)
image_width / image_heightinteger0Resize output (0 = no resize). Preserves aspect ratio

Timing

ParameterTypeDefaultDescription
delayinteger ms0Extra wait after page load (max 10000ms)
wait_untilenumnetworkidle2load · domcontentloaded · networkidle0 · networkidle2
wait_for_selectorstringWait for this CSS selector to appear before capturing

Emulation

ParameterTypeDefaultDescription
dark_modebooleanfalseForce prefers-color-scheme: dark
reduced_motionbooleanfalseEnable prefers-reduced-motion: reduce
media_typeenumscreenscreen or print
timezonestringIANA timezone, e.g. America/New_York
geolocation_latitudefloatLatitude (−90 to 90)
geolocation_longitudefloatLongitude (−180 to 180)
geolocation_accuracyinteger m100Geolocation accuracy radius in meters
user_agentstringCustom User-Agent string

Injection & Interaction

ParameterTypeDescription
headersJSON stringExtra HTTP request headers, e.g. {"Authorization":"Bearer token"}
cookiesJSON stringCookies array, e.g. [{"name":"s","value":"abc","domain":"example.com"}]
stylesstringCSS to inject into the page before capture
scriptsstringJavaScript to inject into the page before capture
hide_selectorsstringComma-separated CSS selectors to hide (sets display:none)
clickstringCSS selector to click before capturing

Blocking

ParameterDefaultDescription
block_adsfalseBlock ads (powered by Ghostery)
block_trackersfalseBlock trackers (powered by Ghostery)
block_cookie_bannersfalseBlock cookie consent banners
block_requestsComma-separated URL patterns to block, e.g. analytics.js,hotjar.com

PDF Options

ParameterDefaultDescription
pdf_print_backgroundfalseInclude background graphics in PDF
pdf_landscapefalseLandscape orientation
pdf_paper_formata4a4 · a3 · a2 · a1 · letter · legal · tabloid

Content Extraction

When extract_content=true, the response includes a content field alongside the image. Only available with response_type=json or base64.

ParameterDefaultDescription
extract_contentfalseExtract page body as text
content_formatmarkdownhtml · markdown · text

Metadata Extraction

When metadata=true, the response includes a metadata object with title, OG tags, and favicon. Cache is bypassed for metadata requests.

"metadata": { "title": "Page Title", "description": "Meta description", "og_title": "OG Title", "og_image": "https://example.com/og.png", "og_description": "OG description", "favicon": "https://example.com/favicon.ico", "http_status": 200 }

Async / Webhook

ParameterDefaultDescription
asyncfalseReturn 202 with job_id immediately, process in background
webhook_urlPOST result JSON to this URL when the job completes

Cache & Response

ParameterDefaultDescription
cachetrueSet to false to force a fresh screenshot
response_typeimageimage — binary · json — metadata only · base64 — image in JSON

Response Headers

HeaderDescription
X-CacheHIT if served from cache, MISS if freshly captured
Cache-Controlpublic, max-age=<CACHE_TTL_SECONDS>
ETagCache key SHA-256 hash

POST /batch

Capture screenshots of up to 10 URLs in a single request. Processed in parallel. Each URL is captured independently — if one fails, others still complete.

Request Body (JSON)

FieldTypeDefaultDescription
urlsstring[]Array of URLs (1–10) required
formatenumpngOutput format for all URLs
width / heightinteger1280 / 800Viewport dimensions
qualityinteger80Compression quality (jpeg/webp)
full_pagebooleanfalseFull page capture
response_typeenumbase64base64 · paths · image_urls

Response

{ "total": 3, "results": [ { "url": "https://...", "format": "png", "cached": false, "image": "data:image/png;base64,..." }, { "url": "https://...", "error": "Navigation timeout" } // per-URL error ] }
SSRF protection applies to all URLs upfront. If any URL targets a private address, the entire request returns 403.

GET /diff

Pixel-level visual comparison between two URLs. Both pages are captured in parallel, then compared with pixelmatch. Changed pixels are highlighted in red in the diff image.

Query Parameters

ParameterDefaultDescription
beforeURL of the "before" state required
afterURL of the "after" state required
width / height1280 / 800Viewport dimensions for both captures
threshold0.1Sensitivity (0–1). Lower = more sensitive to subtle changes
response_typeimageimage — PNG · json — stats only · base64 — image + stats

JSON Response (response_type=json)

{ "before": "https://myapp.com/v1", "after": "https://myapp.com/v2", "width": 1280, "height": 800, "changed_pixels": 14320, "total_pixels": 1024000, "diff_percent": 1.40, "has_changes": true }

GET /jobs/:id

Check the status of an async screenshot job. Jobs expire from memory after 1 hour.

Job Statuses

pending processing done | failed

Success

{ "id": "550e8400...", "status": "done", "createdAt": "2024-01-15T12:00:00Z", "completedAt": "2024-01-15T12:00:03Z", "result": { "url": "https://example.com/", "format": "png", "storagePath": "storage/..." } }

Failure

{ "id": "550e8400...", "status": "failed", "createdAt": "2024-01-15T12:00:00Z", "completedAt": "2024-01-15T12:00:05Z", "error": "Page load timed out after 30000ms" }

GET /health

Returns service health — no auth required.

{ "status": "ok", "browserPool": { "size": 3, "available": 2 }, "cache": "redis" }

Environment Variables

VariableDefaultDescription
PORT3000Port to listen on
API_KEYSComma-separated API keys. Empty = auth disabled
SAVE_TO_DISKfalseSave screenshots to disk and return path instead of binary
LOCAL_STORAGE_PATH./storageDirectory for saved screenshots
STORAGE_TYPElocallocal or s3
REDIS_URLRedis connection URL. Falls back to in-memory if not set
CACHE_TTL_SECONDS3600Cache TTL in seconds
BROWSER_MAX_CONCURRENT3Browser pool size. Each instance uses ~300MB RAM
SCREENSHOT_TIMEOUT_MS30000Max page load time in ms
MAX_REQUESTS_PER_MINUTE60Rate limit per API key/IP
CORS_ORIGINRestrict CORS, e.g. https://yourdomain.com. Omit to allow all
S3_BUCKETS3 bucket name
S3_ENDPOINTS3 endpoint URL (for MinIO, Cloudflare R2, etc.)
S3_REGIONus-east-1S3 region
S3_ACCESS_KEY / S3_SECRET_KEYS3 credentials

MCP Server

The backend ships an MCP (Model Context Protocol) server — connect Claude Desktop, Cursor, or any MCP-compatible LLM client directly to the screenshot API. Claude can then call screenshot, batch_screenshot, visual_diff, and check_job as native tools.

1. Build

npm run build # compiles src/mcp.ts → dist/mcp.js

2. Configure Claude Desktop

Add to ~/.claude/claude_desktop_config.json:

{ "mcpServers": { "screenshot": { "command": "node", "args": ["/absolute/path/to/screenshot-backend/dist/mcp.js"], "env": { "SCREENSHOT_API_URL": "http://localhost:3000", "SCREENSHOT_API_KEY": "your-api-key" } } } }

Available MCP Tools

ToolDescription
screenshotCapture a URL or HTML — returns image + metadata JSON
batch_screenshotCapture up to 10 URLs in parallel — returns one image per URL
visual_diffPixel-level diff between two URLs — returns diff image + change stats
check_jobCheck status of an async screenshot job
The MCP server connects to the running HTTP API — start the screenshot service first (docker compose up or npm run dev), then restart Claude Desktop.

Docker

Full stack with Redis

API_KEYS=mykey123 docker compose up

Custom pool size

BROWSER_MAX_CONCURRENT=5 API_KEYS=mykey123 docker compose up
The Docker image uses system Chromium (PUPPETEER_SKIP_DOWNLOAD=true) and runs as a non-root node user. shm_size: 1gb is set in docker-compose to prevent Chrome crashes on complex pages.