API Reference

Full documentation for the ScreenshotAPI HTTP API. Base URL: https://api.snapshotflow.com

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
proxystringComing soonManaged proxy profile or explicit proxy endpoint for network-origin targeting (not live yet)
proxy_countrystringComing soonISO country code for proxy-based local screenshots, e.g. DE or US (not live yet)
proxy_regionstringComing soonRegion or state code for proxy targeting when supported (not live yet)
proxy_citystringComing soonCity-level proxy targeting when supported (not live yet)

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

SnapshotFlow now exposes MCP directly from the backend at https://api.snapshotflow.com/mcp. This is the primary remote MCP endpoint for agents and MCP-compatible clients.

Recommended setup: connect your client to the hosted MCP endpoint on api.snapshotflow.com. A local stdio MCP wrapper still exists for development, but most users should use the server MCP endpoint.

Remote MCP endpoint

https://api.snapshotflow.com/mcp

How it works

  • The MCP transport is served by the backend itself.
  • The MCP tools call the same screenshot engine and storage layer as the HTTP API.
  • Available tools are screenshot, batch_screenshot, visual_diff, and check_job.

Configure a local stdio wrapper (optional)

If your MCP client expects a local process, you can still use the bundled stdio wrapper. Example for ~/.claude/claude_desktop_config.json:

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

Point SCREENSHOT_API_URL at your backend, for example https://api.snapshotflow.com. If your API requires authentication, set SCREENSHOT_API_KEY too.

Available MCP tools

ToolDescription
screenshotCapture one URL or base64 HTML. Returns an image plus metadata, content, and cache info.
batch_screenshotCapture up to 10 URLs in one request. Returns one image result per URL.
visual_diffCompare two URLs visually and return a diff image with change statistics.
check_jobCheck the status of an async screenshot job created by the HTTP API.

Run locally in development

npm run dev
npm run mcp:dev
The local stdio wrapper does not embed the browser pool or Fastify app. It forwards tool calls to the running screenshot backend via SCREENSHOT_API_URL, so keep the HTTP API online while your MCP client is connected.

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.

Coming Soon roadmap

Video capture — GET /video

Record a page interaction as an MP4 or WebM file. Supports scroll/click action replay, configurable duration and FPS, and the same response_type convention as /screenshot — stream the binary directly or get a storage path / async job_id.

Planned Parameters

ParameterTypeDefaultDescription
urlstringPage to record required
durationinteger s5Recording duration in seconds (max 60)
fpsinteger25Frames per second
formatenummp4mp4 or webm
width / heightinteger1280 / 800Viewport dimensions
actionsJSON arrayScroll / click actions to replay during recording, e.g. [{"type":"scroll","y":800,"delay":1000}]
The response will follow the same response_type convention — stream the binary file or return a JSON with a storage path / async job_id.