What Is a Screenshot API? How One Request Works

A screenshot API turns a URL or HTML into an image or PDF over HTTP. See what happens inside one request, what it replaces, and the code on both sides.

By Simon Chiu · Updated

A screenshot API is a web service that loads a URL or a piece of HTML in a hosted browser and returns the rendered result, usually a PNG, JPEG, WebP or PDF, in response to a single HTTP request.

It turns "run a browser" into "call an endpoint". The service owns the headless browsers, the fonts, the wait logic, the caching and the scaling. Your code sends options such as viewport, format and wait condition, and gets bytes or a link back. This entry is part of the screenshot and rendering glossary.

A plain explanation

Taking one screenshot with Puppeteer takes a dozen lines. Taking a million reliably takes a small platform: a browser pool, crash recovery, queues, timeouts, font packages, cookie-banner handling, storage and a cache so the same page is not rendered twice. A screenshot API is that platform, run by someone else, behind a URL.

Concern Running your own browser Screenshot API
Browser install and updates You build and patch the image Provider
Concurrency and crashed tabs Your pool, your restarts Provider
Waiting for SPAs, lazy images, fonts Your code per site Request options
Cookie banners and ads Your selectors Blocking options
Storage and CDN links Your bucket and cache rules Returned with the image
Full control of every step Yes Within the option set
Cost model Servers, whether busy or idle Per capture

What happens inside one request

Your appone request APIkey, options, creditscache lookup Cache hitstored image Cache missbrowser loads, waits, captures Image +X-Cache-URL
The path of one RenderScreenshot request. A cache hit skips the browser entirely.

When we built RenderScreenshot, we split that path in two. The API application handles everything that is business logic: authentication, validation of every option, credits and the cache lookup. Only a cache miss reaches the browser, which runs on Cloudflare's Browser Rendering platform. The rendered file goes straight to R2 storage, not back through the API servers, and the response carries an X-Cache-URL header with a public CDN link to the same image. See response headers.

The same capture, three ways

Puppeteer, run by you. The smallest version that does not leak browsers.

import puppeteer from 'puppeteer';
import { writeFile } from 'node:fs/promises';

export async function screenshot(url, { width = 1280, height = 800 } = {}) {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.setViewport({ width, height });
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 30_000 });
    return await page.screenshot({ type: 'png' });
  } finally {
    await browser.close(); // a leaked browser is the most common DIY outage
  }
}

const png = await screenshot(process.argv[2] ?? 'https://example.com');
await writeFile('diy.png', png);
console.log(`diy.png ${png.length} bytes`);

Playwright, run by you.

import { chromium } from 'playwright';
import { writeFile } from 'node:fs/promises';

export async function screenshot(url, { width = 1280, height = 800 } = {}) {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage({ viewport: { width, height } });
    await page.goto(url, { waitUntil: 'networkidle', timeout: 30_000 });
    return await page.screenshot({ type: 'png' });
  } finally {
    await browser.close();
  }
}

const png = await screenshot(process.argv[2] ?? 'https://example.com');
await writeFile('diy-playwright.png', png);
console.log(`diy-playwright.png ${png.length} bytes`);

Both work, and both are the start of the work, not the end. Neither handles concurrency, retries, fonts, banners or storage.

RenderScreenshot. One request. takeJson returns the CDN URL and image details instead of bytes, which is what you store in a database.

import { Client, TakeOptions } from 'renderscreenshot';

const client = new Client(process.env.RS_API_KEY);
const result = await client.takeJson(
  TakeOptions.url('https://example.com').width(1280).height(800).waitFor('networkidle').blockCookieBanners(),
);
console.log(result.url, result.width, result.height, result.cached);
curl --get https://api.renderscreenshot.com/v1/screenshot \
  -H "Authorization: Bearer $RS_API_KEY" \
  --data-urlencode 'url=https://example.com' \
  -d width=1280 -d height=800 -d block_cookies=true \
  -D headers.txt --output page.png

The -D flag saves the response headers, so you can read X-Cache-URL and X-Cache-Status from headers.txt.

Tested on 2026-10-15 with Node 22.18, Puppeteer 24.43.1 (run in headless shell mode) and Playwright 1.64.1; the two do-it-yourself functions each wrote a 1280 x 800 PNG of example.com. The RenderScreenshot snippets follow the Node SDK docs and the GET endpoint docs and were not run for this page.

Common mistakes when choosing or using one

  • Judging by the demo URL. Every API captures a static page. Test your hardest pages: SPAs, pages with consent banners, long pages with lazy images.
  • Rendering on every page view. Cache the result or use the returned CDN link. Re-rendering the same page per visitor wastes time and credits.
  • Putting a secret key in public HTML. For image tags and og:image, use signed URLs.
  • Ignoring the output format. WebP is often much smaller than PNG for the same capture. Pick per use case.

To compare providers, read how to choose a screenshot API and screenshot API vs headless browser API. For SDKs in your language, see the screenshot API overview. To make your first request, follow the quick start with a free API key. No credit card is needed.

Frequently asked questions

What is the difference between a screenshot API and a headless browser API?
A screenshot API takes a URL and options and returns a finished image or PDF. A headless browser API gives you a remote browser that your own Puppeteer or Playwright code drives step by step. The first is simpler; the second is more flexible.
When should I run my own headless browser instead?
When captures need long scripted interactions, run inside a private network the API cannot reach, or are so many that a dedicated team is cheaper than per-capture pricing.
Can I use a screenshot API URL directly in an img tag?
With a GET endpoint, yes. Use a signed URL so the page does not expose your API key.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.