DEV Community

CurlShot
CurlShot

Posted on Originally published at curlshot.com

Add website screenshots to your app with one API call (no headless Chrome)

Add website screenshots to your app with one API call (no headless Chrome)

Sooner or later a project needs a picture of a web page: an Open Graph image for social cards, a PDF receipt or report, a thumbnail of a user's site, a visual archive, or a monitoring diff. The "obvious" answer — run Puppeteer or Playwright yourself — turns into a surprising amount of ops work once it hits production.

Why self-hosting headless Chrome hurts

  • Chrome is heavy. Each render spins up a browser; memory spikes, and a few concurrent requests can OOM a small box.
  • The environment fights you. Missing system fonts (blank CJK and emoji), missing shared libraries in slim Docker images, sandbox flags, --no-sandbox footguns.
  • Edge cases pile up. Lazy-loaded images, cookie banners covering the fold, infinite scroll, ad iframes, fonts that load after load.
  • Scaling is its own project. A queue, a pool of browsers, timeouts, retries, and keeping Chrome patched forever.

For a lot of teams that's a week of work to build and then maintain, for something that isn't the product.

The one-call alternative

A screenshot API does the Chrome-wrangling for you: send a URL, get back an image or PDF. Here's the smallest possible version with CurlShot — the API I built after doing the Puppeteer dance one too many times. The free tier is 100 screenshots a month, no card.

curl "https://curlshot.com/api/v1/screenshot?url=https://example.com&access_key=YOUR_KEY" -o shot.png
Enter fullscreen mode Exit fullscreen mode

That's a full PNG of the page on disk. Done.

Add the options you actually need

Everything is a parameter. POST a JSON body when you want more than a quick GET:

curl -X POST https://curlshot.com/api/v1/screenshot \
  -H "X-Access-Key: YOUR_KEY" \
  -H "content-type: application/json" \
  -d '{
    "url": "https://example.com",
    "full_page": true,
    "block_ads": true,
    "block_cookie_banners": true
  }' \
  -o shot.png
Enter fullscreen mode Exit fullscreen mode

A few things that take real effort with raw Chrome and are one field here:

  • PDF instead of an image: "format": "pdf"
  • A phone viewport: "viewport_device": "iphone_15" (there's a /api/v1/devices endpoint listing presets — iPhones, Pixels, iPads, desktops)
  • Just one element: "selector": "#pricing"
  • Dark mode: "dark_mode": true

In Node.js

const res = await fetch("https://curlshot.com/api/v1/screenshot", {
  method: "POST",
  headers: {
    "X-Access-Key": process.env.CURLSHOT_KEY,
    "content-type": "application/json",
  },
  body: JSON.stringify({ url: "https://example.com", full_page: true }),
});

const buffer = Buffer.from(await res.arrayBuffer());
await fs.writeFile("shot.png", buffer);
Enter fullscreen mode Exit fullscreen mode

Generating Open Graph images on the fly? Point it at your own /og?title=... route and cache the result.

In n8n (no code)

If you automate in n8n, there's a community node, n8n-nodes-curlshot, with a CurlShot action and a trigger. Install it from Settings → Community nodes, drop it into a workflow, and you can screenshot a URL as a step — great for "new row in a sheet → screenshot the link → post to Slack." Prefer plain HTTP? An HTTP Request node pointed at the endpoint above works identically.

From an AI agent (MCP)

Agents can take screenshots too. CurlShot exposes an MCP server at https://curlshot.com/api/mcp, so Claude, Cursor, or Cline can "see" a page — handy for letting an agent verify a deploy or read a page that isn't in its training data.

When to self-host anyway

If you render millions of pages, have strict data-residency rules, or need a custom Chrome build, run your own. For everything else, an API call is cheaper than the ops time.

Either way, the lesson that took me too long: screenshotting the web is a solved problem — don't let it become your problem.

(Full disclosure: I build CurlShot. Docs are at https://curlshot.com/docs — happy to answer questions in the comments.)

Top comments (0)