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)
Via query param
Error Responses
All errors return JSON with a consistent shape:
| HTTP | Code | Cause |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing or invalid parameters |
| 400 | INVALID_URL | URL is malformed or not http/https |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 403 | BLOCKED | URL targets private/loopback address (SSRF) |
| 408 | TIMEOUT | Page load exceeded SCREENSHOT_TIMEOUT_MS |
| 422 | NAVIGATION_FAILED | Browser could not load the page |
| 422 | SELECTOR_NOT_FOUND | selector, click, or wait_for_selector not found |
| 429 | RATE_LIMITED | Too many requests — see retryAfter in response |
| 503 | POOL_EXHAUSTED | All 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.
{ "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.
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
| Parameter | Type | Default | Description |
|---|---|---|---|
| url | string | — | Page URL to capture (http/https only). Required unless html is provided required* |
| html | string | — | Base64-encoded HTML to render instead of a URL optional |
Viewport
| Parameter | Type | Default | Description |
|---|---|---|---|
| width | integer | 1280 | Viewport width in px (100–3840) |
| height | integer | 800 | Viewport height in px (100–2160) |
| device_scale_factor | float | 1 | Device pixel ratio. Use 2 for retina |
| viewport_mobile | boolean | false | Enable mobile emulation (touch events, mobile meta tag) |
Capture
| Parameter | Type | Default | Description |
|---|---|---|---|
| format | enum | png | png · jpeg · webp · pdf |
| quality | integer | 80 | 1–100. Applies to jpeg and webp only |
| full_page | boolean | false | Capture the full scrollable height of the page |
| omit_background | boolean | false | Transparent background (PNG only) |
| selector | string | — | CSS selector — capture only that element |
| clip_x / clip_y | integer | 0 | Clip region top-left corner in px |
| clip_width / clip_height | integer | 0 | Clip region size (0 = disabled) |
| image_width / image_height | integer | 0 | Resize output (0 = no resize). Preserves aspect ratio |
Timing
| Parameter | Type | Default | Description |
|---|---|---|---|
| delay | integer ms | 0 | Extra wait after page load (max 10000ms) |
| wait_until | enum | networkidle2 | load · domcontentloaded · networkidle0 · networkidle2 |
| wait_for_selector | string | — | Wait for this CSS selector to appear before capturing |
Emulation
| Parameter | Type | Default | Description |
|---|---|---|---|
| dark_mode | boolean | false | Force prefers-color-scheme: dark |
| reduced_motion | boolean | false | Enable prefers-reduced-motion: reduce |
| media_type | enum | screen | screen or print |
| timezone | string | — | IANA timezone, e.g. America/New_York |
| geolocation_latitude | float | — | Latitude (−90 to 90) |
| geolocation_longitude | float | — | Longitude (−180 to 180) |
| geolocation_accuracy | integer m | 100 | Geolocation accuracy radius in meters |
| user_agent | string | — | Custom User-Agent string |
Injection & Interaction
| Parameter | Type | Description |
|---|---|---|
| headers | JSON string | Extra HTTP request headers, e.g. {"Authorization":"Bearer token"} |
| cookies | JSON string | Cookies array, e.g. [{"name":"s","value":"abc","domain":"example.com"}] |
| styles | string | CSS to inject into the page before capture |
| scripts | string | JavaScript to inject into the page before capture |
| hide_selectors | string | Comma-separated CSS selectors to hide (sets display:none) |
| click | string | CSS selector to click before capturing |
Blocking
| Parameter | Default | Description |
|---|---|---|
| block_ads | false | Block ads (powered by Ghostery) |
| block_trackers | false | Block trackers (powered by Ghostery) |
| block_cookie_banners | false | Block cookie consent banners |
| block_requests | — | Comma-separated URL patterns to block, e.g. analytics.js,hotjar.com |
PDF Options
| Parameter | Default | Description |
|---|---|---|
| pdf_print_background | false | Include background graphics in PDF |
| pdf_landscape | false | Landscape orientation |
| pdf_paper_format | a4 | a4 · 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.
| Parameter | Default | Description |
|---|---|---|
| extract_content | false | Extract page body as text |
| content_format | markdown | html · 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.
Async / Webhook
| Parameter | Default | Description |
|---|---|---|
| async | false | Return 202 with job_id immediately, process in background |
| webhook_url | — | POST result JSON to this URL when the job completes |
Cache & Response
| Parameter | Default | Description |
|---|---|---|
| cache | true | Set to false to force a fresh screenshot |
| response_type | image | image — binary · json — metadata only · base64 — image in JSON |
Response Headers
| Header | Description |
|---|---|
| X-Cache | HIT if served from cache, MISS if freshly captured |
| Cache-Control | public, max-age=<CACHE_TTL_SECONDS> |
| ETag | Cache 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)
| Field | Type | Default | Description |
|---|---|---|---|
| urls | string[] | — | Array of URLs (1–10) required |
| format | enum | png | Output format for all URLs |
| width / height | integer | 1280 / 800 | Viewport dimensions |
| quality | integer | 80 | Compression quality (jpeg/webp) |
| full_page | boolean | false | Full page capture |
| response_type | enum | base64 | base64 · paths · image_urls |
Response
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
| Parameter | Default | Description |
|---|---|---|
| before | — | URL of the "before" state required |
| after | — | URL of the "after" state required |
| width / height | 1280 / 800 | Viewport dimensions for both captures |
| threshold | 0.1 | Sensitivity (0–1). Lower = more sensitive to subtle changes |
| response_type | image | image — PNG · json — stats only · base64 — image + stats |
JSON Response (response_type=json)
GET /jobs/:id
Check the status of an async screenshot job. Jobs expire from memory after 1 hour.
Job Statuses
Success
Failure
GET /health
Returns service health — no auth required.
Environment Variables
| Variable | Default | Description |
|---|---|---|
| PORT | 3000 | Port to listen on |
| API_KEYS | — | Comma-separated API keys. Empty = auth disabled |
| SAVE_TO_DISK | false | Save screenshots to disk and return path instead of binary |
| LOCAL_STORAGE_PATH | ./storage | Directory for saved screenshots |
| STORAGE_TYPE | local | local or s3 |
| REDIS_URL | — | Redis connection URL. Falls back to in-memory if not set |
| CACHE_TTL_SECONDS | 3600 | Cache TTL in seconds |
| BROWSER_MAX_CONCURRENT | 3 | Browser pool size. Each instance uses ~300MB RAM |
| SCREENSHOT_TIMEOUT_MS | 30000 | Max page load time in ms |
| MAX_REQUESTS_PER_MINUTE | 60 | Rate limit per API key/IP |
| CORS_ORIGIN | — | Restrict CORS, e.g. https://yourdomain.com. Omit to allow all |
| S3_BUCKET | — | S3 bucket name |
| S3_ENDPOINT | — | S3 endpoint URL (for MinIO, Cloudflare R2, etc.) |
| S3_REGION | us-east-1 | S3 region |
| S3_ACCESS_KEY / S3_SECRET_KEY | — | S3 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
2. Configure Claude Desktop
Add to ~/.claude/claude_desktop_config.json:
Available MCP Tools
| Tool | Description |
|---|---|
| screenshot | Capture a URL or HTML — returns image + metadata JSON |
| batch_screenshot | Capture up to 10 URLs in parallel — returns one image per URL |
| visual_diff | Pixel-level diff between two URLs — returns diff image + change stats |
| check_job | Check status of an async screenshot job |
docker compose up or npm run dev), then restart Claude Desktop.
Docker
Full stack with Redis
Custom pool size
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.