Self-hosted · No limits · Full control

Capture Any Page.
One Request.

A production-ready screenshot API built on Puppeteer and Fastify. Full pages, PDFs, visual diffs, batch captures, async webhooks — everything you need in a single self-hosted service.

terminal
# Screenshot → binary PNG
curl "http://localhost:3000/screenshot?url=https://github.com" \
-H "X-Api-Key: mykey" --output github.png
# Batch 3 URLs → base64 array
curl -X POST "http://localhost:3000/batch" \
-d '{"urls":["https://github.com","https://stripe.com","https://vercel.com"]}'
# Visual diff → diff_percent + highlighted image
curl "http://localhost:3000/diff?before=https://v1.app&after=https://v2.app&response_type=json"
4
API Endpoints
40+
Parameters
PNG/JPEG/
WebP/PDF
Output formats
Redis +
Memory
Caching backends

Everything You Need to Capture the Web

Not just screenshots. A full suite of browser automation primitives behind a clean HTTP API.

Full-Page & Viewport Capture

Capture the full scrollable page or a precise viewport. Control width, height, device scale factor, and mobile emulation.

Batch Capture

POST up to 10 URLs in one request. Processed in parallel via the browser pool. Returns base64, paths, or image URLs.

Visual Diff

Pixel-level comparison between two URLs. Returns a highlighted diff image and change percentage — perfect for regression testing.

Async + Webhooks

Add async=true and get a job ID back immediately. Poll /jobs/:id or receive a webhook when done.

PDF Export

Render any page to PDF with configurable paper format (A4, Letter, Tabloid), orientation, and background graphics.

Ad & Cookie Blocking

Powered by Ghostery. Block ads, trackers, and cookie consent banners for clean screenshots without visual clutter.

Content Extraction

Extract page body as Markdown, HTML, or plain text alongside the screenshot. Perfect for LLM pipelines that need both vision and text.

Dark Mode & Emulation

Force dark color scheme, emulate timezone, geolocation, mobile viewport, custom User-Agent, and reduced motion.

SSRF Protection + Auth

Blocks all private IP ranges and loopback addresses. API key auth with per-key rate limiting via X-Api-Key.

See It In Action

Real requests, real responses. Paste and run.

Request

# PNG — binary output curl "http://localhost:3000/screenshot ?url=https://stripe.com &full_page=true &format=png &width=1440" \ -H "X-Api-Key: mykey" \ --output stripe.png

What You Get

200 OK · image/png
X-Cache: MISS
Cache-Control: public, max-age=3600
ETag: ss:766de460...
// Binary PNG body (~350KB full page)
# Same URL → served from cache instantly curl "http://localhost:3000/screenshot?url=https://stripe.com&full_page=true" # X-Cache: HIT

Request

curl -X POST "http://localhost:3000/batch" \ -H "Content-Type: application/json" \ -H "X-Api-Key: mykey" \ -d '{ "urls": [ "https://stripe.com", "https://vercel.com", "https://tailwindcss.com" ], "format": "jpeg", "quality": 85, "width": 1280, "response_type": "base64" }'

Response

{ "total": 3, "results": [ { "url": "https://stripe.com/", "format": "jpeg", "cached": false, "image": "data:image/jpeg;base64,/9j/4AA..." }, { "url": "https://vercel.com/", ... }, { "url": "https://tailwindcss.com/", ... } ] }

Request — JSON stats

curl "http://localhost:3000/diff ?before=https://myapp.com/v1 &after=https://myapp.com/v2 &threshold=0.05 &response_type=json" \ -H "X-Api-Key: mykey"
# Or get the visual diff image curl "http://localhost:3000/diff ?before=https://myapp.com/v1 &after=https://myapp.com/v2" \ --output diff.png

Response

{ "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 }
Use case: CI regression testing Run after every deploy. If diff_percent > 1 — flag the build and send the diff image to Slack.

1. Enqueue

curl "http://localhost:3000/screenshot ?url=https://stripe.com &async=true &webhook_url=https://myapp.com/done &format=pdf" \ -H "X-Api-Key: mykey" # Returns 202 immediately: { "job_id": "550e8400...", "status": "pending" }

2. Poll (optional)

curl "http://localhost:3000/jobs/550e8400..."

3. Webhook payload (POST to your URL)

{ "job_id": "550e8400...", "status": "done", "result": { "url": "https://stripe.com/", "format": "pdf", "storagePath": "storage/2024-01-15/766de460.pdf" } }
Job statuses: pending → processing → done | failed
Jobs stay in memory for 1 hour, then auto-expire.

Screenshot + Markdown in one call

curl "http://localhost:3000/screenshot ?url=https://stripe.com &response_type=base64 &extract_content=true &content_format=markdown &metadata=true"

Response — ready for Claude / GPT-4 Vision

{ "image": "data:image/png;base64,iVBORw0K...", "content": "# Stripe\n\nPayments infrastructure...", "metadata": { "title": "Stripe | Payment Processing Platform", "description": "Millions of companies...", "og_image": "https://stripe.com/og.png" }, "cached": false, "takenAt": "2024-01-15T12:00:00.000Z" }

How It Works

Simple pipeline from HTTP request to cached screenshot.

1

Request

Send a GET/POST with your URL and options. Auth via API key header or query param.

2

Capture

A Puppeteer browser pool loads the page, applies emulations, injections, and captures.

3

Cache & Return

Result is stored in Redis (or memory). Next identical request is served instantly.

Up in 2 Minutes

Clone, configure, and capture. No cloud accounts needed.

1

Docker Compose (recommended — includes Redis)

# Start everything
API_KEYS=mykey123 docker compose up
2

Or run locally with Node.js ≥ 24

cp .env.example .env && npm install && npm run dev
3

Take your first screenshot

curl "http://localhost:3000/screenshot?url=https://example.com" -H "X-Api-Key: mykey123" --output out.png

Built With

Fastify Puppeteer TypeScript Redis Sharp Ghostery Adblocker Pixelmatch Zod Docker Vitest