· Updated · Comparisons · By Simon Chiu

Best Screenshot API Comparison (2026): 10 Services

The best screenshot APIs of 2026 compared: 10 services plus self-hosted Chrome, with a scored checklist, prices checked in October 2026 and a test script.

Line illustration of the RenderScreenshot mascot weighing two browsers on a balance scale

Disclosure: I build RenderScreenshot, one of the ten services on this page. I scored every vendor with the same rules, and I show where we lose.

How this comparison works. I read each vendor's own docs and pricing page on October 14, 2026, and scored ten screenshot services plus self-hosted Chrome on the same checklist. I wrote one request per vendor and tested it: against a local mock server for request shape, and live where a vendor allows keyless calls (Microlink). I timed self-hosted Puppeteer and Playwright on my own machine. I have not run the keyed vendors (RenderScreenshot included) through the full benchmark yet, so this page prints no latency numbers for them. The script to produce those numbers on your own URLs is below.

Last verified: October 15, 2026. Prices: as listed on each vendor's pricing page on October 14, 2026.

The ten services at a glance

Service What you call Response Public link without your key Free allowance Price at 10,000 / month
RenderScreenshot GET or POST /v1/screenshot Image bytes, or JSON Signed URL (max 30 days) and X-Cache-URL 150 credits / month $40 (Growth, 12,000)
ScreenshotOne GET or POST /take Image bytes, or JSON Signed links and a cache URL header 100 / month $79 (Growth, 10,000)
Urlbox Render link, or POST /v1/render/sync Render link: image. REST: JSON Render link with HMAC token 7-day trial $99 (Ultra, 15,000)
ApiFlash GET or POST /v1/urltoimage Image bytes, or JSON No signing; JSON gives a file URL 100 / month $35 (Medium)
ScreenshotAPI.net GET /screenshot Image or JSON Not documented in the pages I read 7-day trial, 100 shots $29 (Startup)
Screenshot Machine GET with key Image Optional MD5 hash of URL + secret phrase 100 / month €37 (Basic + overage)
Microlink GET on api. or pro.microlink.io JSON with a CDN URL (or image with embed) CDN URL in the JSON 100 requests / month €39 (Pro, 46,000)
Browserless Puppeteer/Playwright connect, or POST /screenshot Image No 1,000 units / month $25 (annual billing, 20,000 units)
Cloudflare Browser Run Workers binding, or REST Quick Action Image No 10 browser minutes / day About $5 to $7 (time-billed)
ScrapingBee GET /api/v1/?screenshot=true Image or JSON No 1,000 trial credits $19 (Hobby, 75,000 credits; 5 credits per rendered shot)
Self-hosted Puppeteer / Playwright Your code Whatever you write Whatever you build Free software Your server and your time

The full price grid at 1,000, 10,000 and 100,000 screenshots, with the billing rules that change the totals, is in the screenshot API pricing comparison.

Best for, by job

  • Social cards and og:image tags: RenderScreenshot. The og_card preset (1200×630) and signed URLs that you can paste into a meta tag cover the whole job. Urlbox render links are the strongest alternative, and they do not expire.
  • Widest option list, video and agent tooling: ScreenshotOne or Urlbox. Both document video output and an MCP server for AI agents. RenderScreenshot has no video output and no MCP server today.
  • Lowest bill at 100,000 shots a month: ScrapingBee, Microlink or Cloudflare Browser Run, by list price. Each bills a different unit, so check the pricing page before you trust the ranking.
  • Lowest bill at 1,000 shots a month: Browserless (free tier) or ApiFlash ($7).
  • Full browser control: Browserless or Cloudflare Browser Run, or Puppeteer and Playwright on your own servers.
  • A Python, Ruby, PHP, Java or Go backend that wants a typed client: RenderScreenshot ships official SDKs in all six languages; see the SDK overview.
  • Metadata and link previews in one call: Microlink returns page metadata and the screenshot in the same JSON.

The scored checklist

Every vendor gets the same eight questions. Yes means the feature is in the vendor's own docs. Partial means a workaround or a plan restriction. No means I found no such feature. ? means the pages I read did not say, so I did not score it.

Criterion RenderScreenshot ScreenshotOne Urlbox ApiFlash Microlink Browserless Cloudflare Screenshot Machine
Image bytes in one request Yes Yes Partial (render links; REST returns JSON) Yes Partial (embed) Yes Yes Yes
Signed public link that hides your key Yes Yes Yes No Partial (CDN URL after a call) No No Partial (MD5 hash, no expiry)
Cache hits that do not count against quota Yes (cache.available) Yes Yes (render links) Yes Yes No cache No cache Yes
API to delete or purge cached images Yes No (change cache_key) ? No (use fresh) ? No cache No cache ?
Block ads and cookie banners by flag Yes Yes Yes Yes Yes Partial (request rules) ? ?
Use-case presets (OG card, mobile, HD) Yes (10 presets) Partial (device presets) ? No Partial (device) No No Partial (device)
Batch or async jobs with webhooks Yes Yes Yes No ? ? ? ?
Official SDKs 6 languages Yes Yes Code samples Node client Puppeteer / Playwright Cloudflare SDK Code samples

Weight these rows by your own needs. The scored checklist for choosing a screenshot API has a worksheet for that.

What I measured, and what I did not

Method box.

  • Date: October 15, 2026.
  • Client: Apple M1 Pro, macOS 27.0.1, Node.js 22.18.0, network in Toronto, Canada (Cloudflare colo YYZ).
  • Target: https://example.com/, viewport 1280×800, PNG, device scale factor 1.
  • Cold vs warm: "cold" is the first request for a unique copy of the URL (a random rsbench query parameter defeats every cache). "Warm" is the identical request repeated at once.
  • Success: HTTP 200 and the body starts with PNG, JPEG or WebP magic bytes.
  • Percentiles: nearest-rank p50 and p95 of successful requests.

Results I can publish today.

Run n Cold Warm Output
Microlink, keyless free endpoint 4 757 to 1,181 ms (p50 1,006 ms) 45 to 99 ms (p50 49 ms) 70,539 bytes, 4 of 4 succeeded
Self-hosted Puppeteer 24.43.1 (Chrome 148), new browser per shot vs reused 10 p50 1,029 ms, p95 1,628 ms p50 189 ms, p95 272 ms 81,234 bytes
Self-hosted Playwright 1.64.1 (Chromium 156), new browser per shot vs reused 10 p50 499 ms, p95 533 ms p50 262 ms, p95 297 ms 80,922 bytes

Two things in that table are worth more than the numbers. First, Microlink renders at device scale factor 2 by default: the same request without viewport.deviceScaleFactor=1 gave a 2560×1600 PNG of 168,630 bytes instead of 1280×800 and 70,539 bytes. If you compare vendors without forcing 1x, Microlink looks heavier than it is. Second, the self-hosted rows are on a laptop with no network hop between your code and the browser, so they are a floor, not a fair head-to-head with a hosted API.

Not yet measured: RenderScreenshot, ScreenshotOne, Urlbox, ApiFlash, ScreenshotAPI.net, Screenshot Machine, Browserless, Cloudflare and ScrapingBee all need a key or a paid account. I will add their rows when the run is done, with the raw CSV, and I will keep the rows where RenderScreenshot loses. Until then, do not trust any "fastest screenshot API" claim that has no method box, ours included.

Run the comparison on your own URLs

This is the script behind the table. It has one adapter per vendor, already mapped to each vendor's documented parameters, so every provider gets a 1280×800 PNG of the same page. Vendors without a key in the environment are skipped. It writes one CSV row per request and prints p50, p95, success rate and median bytes per provider and phase.

#!/usr/bin/env node
// compare.mjs: run the same screenshot request against several providers and write a CSV.
// Node 22+, no dependencies. Keys come from environment variables; providers without keys are skipped.
//
//   node compare.mjs --providers renderscreenshot,apiflash --urls urls.txt --runs 3 --out results.csv
//
// "cold" = first request for a cache-busted copy of the URL; "warm" = the identical request repeated.
import { readFileSync, writeFileSync } from 'node:fs';
import { parseArgs } from 'node:util';

const { values: args } = parseArgs({
  options: {
    providers: { type: 'string', default: 'renderscreenshot,screenshotone,urlbox,apiflash,microlink,browserless,cloudflare' },
    urls: { type: 'string', default: 'urls.txt' },
    runs: { type: 'string', default: '3' },
    out: { type: 'string', default: 'results.csv' },
    timeout: { type: 'string', default: '60000' },
  },
});

const env = process.env;
const W = 1280, H = 800;
const base = (name, fallback) => env[`${name}_BASE_URL`] ?? fallback;

// Each adapter returns a fetch() Response whose body is the image, or null when it has no credentials.
const providers = {
  async renderscreenshot(url, signal) {
    if (!env.RS_API_KEY) return null;
    return fetch(`${base('RS', 'https://api.renderscreenshot.com')}/v1/screenshot`, {
      method: 'POST', signal,
      headers: { Authorization: `Bearer ${env.RS_API_KEY}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        url, viewport: { width: W, height: H }, output: { format: 'png' },
        wait: { until: 'load' }, cache: { available: true },
      }),
    });
  },
  async screenshotone(url, signal) {
    if (!env.SCREENSHOTONE_ACCESS_KEY) return null;
    const q = new URLSearchParams({ access_key: env.SCREENSHOTONE_ACCESS_KEY, url, viewport_width: W, viewport_height: H, format: 'png', wait_until: 'load', cache: 'true' });
    return fetch(`${base('SCREENSHOTONE', 'https://api.screenshotone.com')}/take?${q}`, { signal });
  },
  async urlbox(url, signal) {
    if (!env.URLBOX_SECRET) return null;
    const r = await fetch(`${base('URLBOX', 'https://api.urlbox.com')}/v1/render/sync`, {
      method: 'POST', signal,
      headers: { Authorization: `Bearer ${env.URLBOX_SECRET}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({ url, width: W, height: H, format: 'png', wait_until: 'loaded' }),
    });
    if (!r.ok) return r;
    const { renderUrl } = await r.json(); // Urlbox answers with JSON; the image is a second hop.
    return fetch(renderUrl, { signal });
  },
  async apiflash(url, signal) {
    if (!env.APIFLASH_ACCESS_KEY) return null;
    const q = new URLSearchParams({ access_key: env.APIFLASH_ACCESS_KEY, url, width: W, height: H, format: 'png', wait_until: 'page_loaded' });
    return fetch(`${base('APIFLASH', 'https://api.apiflash.com')}/v1/urltoimage?${q}`, { signal });
  },
  async microlink(url, signal) {
    // Works without a key on the free endpoint (small daily quota). MICROLINK_API_KEY switches to pro.
    // Microlink renders at device scale factor 2 unless told otherwise; force 1 so sizes compare fairly.
    const keyed = Boolean(env.MICROLINK_API_KEY);
    const q = new URLSearchParams({ url, screenshot: 'true', 'viewport.width': W, 'viewport.height': H, 'viewport.deviceScaleFactor': 1, 'screenshot.type': 'png', waitUntil: 'load', embed: 'screenshot.url' });
    const host = base('MICROLINK', keyed ? 'https://pro.microlink.io' : 'https://api.microlink.io');
    return fetch(`${host}/?${q}`, { signal, headers: keyed ? { 'x-api-key': env.MICROLINK_API_KEY } : {} });
  },
  async browserless(url, signal) {
    if (!env.BROWSERLESS_TOKEN) return null;
    return fetch(`${base('BROWSERLESS', 'https://production-sfo.browserless.io')}/screenshot?token=${encodeURIComponent(env.BROWSERLESS_TOKEN)}`, {
      method: 'POST', signal,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ url, viewport: { width: W, height: H }, options: { type: 'png' }, gotoOptions: { waitUntil: 'load' } }),
    });
  },
  async cloudflare(url, signal) {
    if (!env.CF_ACCOUNT_ID || !env.CF_API_TOKEN) return null;
    return fetch(`${base('CF', 'https://api.cloudflare.com')}/client/v4/accounts/${env.CF_ACCOUNT_ID}/browser-run/screenshot`, {
      method: 'POST', signal,
      headers: { Authorization: `Bearer ${env.CF_API_TOKEN}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({ url, viewport: { width: W, height: H }, screenshotOptions: { type: 'png' }, gotoOptions: { waitUntil: 'load' } }),
    });
  },
};

const isImage = (buf) =>
  (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) || // PNG
  (buf[0] === 0xff && buf[1] === 0xd8) ||                                          // JPEG
  (buf.subarray(0, 4).toString() === 'RIFF' && buf.subarray(8, 12).toString() === 'WEBP');

async function measure(name, url) {
  const t0 = performance.now();
  const signal = AbortSignal.timeout(Number(args.timeout));
  try {
    const res = await providers[name](url, signal);
    if (res === null) return null;
    const buf = Buffer.from(await res.arrayBuffer());
    const ms = Math.round(performance.now() - t0);
    const ok = res.ok && isImage(buf);
    return { status: res.status, ok, ms, bytes: ok ? buf.length : 0, error: ok ? '' : buf.subarray(0, 120).toString().replace(/[\s,]+/g, ' ') };
  } catch (err) {
    return { status: 0, ok: false, ms: Math.round(performance.now() - t0), bytes: 0, error: String(err.name ?? err) };
  }
}

const pct = (xs, p) => {
  if (!xs.length) return '';
  const s = [...xs].sort((a, b) => a - b);
  return s[Math.min(s.length - 1, Math.ceil((p / 100) * s.length) - 1)];
};

const urls = readFileSync(args.urls, 'utf8').split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#'));
const names = args.providers.split(',').map((s) => s.trim()).filter(Boolean);
for (const n of names) if (!providers[n]) throw new Error(`unknown provider: ${n}`);

const runId = Date.now().toString(36);
const rows = [['date', 'provider', 'url', 'phase', 'run', 'http_status', 'ok', 'ms', 'bytes', 'error']];
const date = new Date().toISOString();

for (const name of names) {
  for (const target of urls) {
    for (let run = 1; run <= Number(args.runs); run++) {
      const u = new URL(target);
      u.searchParams.set('rsbench', `${runId}-${run}`); // unique URL, so the first call cannot hit a cache
      for (const phase of ['cold', 'warm']) {
        const m = await measure(name, u.toString());
        if (m === null) { console.error(`skip ${name}: no credentials`); break; }
        rows.push([date, name, target, phase, run, m.status, m.ok, m.ms, m.bytes, m.error]);
      }
      if (rows.at(-1)[1] !== name) break;
    }
    if (rows.at(-1)[1] !== name) break;
  }
}

const csv = rows.map((r) => r.map((v) => (/[",\n]/.test(String(v)) ? `"${String(v).replace(/"/g, '""')}"` : v)).join(',')).join('\n') + '\n';
writeFileSync(args.out, csv);

// Summary per provider and phase: p50/p95 of successful requests, success rate, median bytes.
console.log('provider,phase,n,success_rate,p50_ms,p95_ms,median_bytes');
for (const name of names) {
  for (const phase of ['cold', 'warm']) {
    const r = rows.slice(1).filter((x) => x[1] === name && x[3] === phase);
    if (!r.length) continue;
    const okRows = r.filter((x) => x[6] === true);
    const ms = okRows.map((x) => x[7]);
    console.log([name, phase, r.length, (okRows.length / r.length).toFixed(2), pct(ms, 50), pct(ms, 95), pct(okRows.map((x) => x[8]), 50)].join(','));
  }
}
const failed = rows.slice(1).some((x) => x[6] === false);
process.exitCode = failed ? 1 : 0;

Run it like this. Put 20 or more of your own URLs in urls.txt, one per line, and run from the region where your production code runs:

export RS_API_KEY=rs_live_...            # and SCREENSHOTONE_ACCESS_KEY, APIFLASH_ACCESS_KEY, ...
node compare.mjs --providers renderscreenshot,screenshotone,apiflash --urls urls.txt --runs 3 --out results.csv

Tested on October 15, 2026 with Node.js 22.18.0: all seven adapters against a local mock that checks each vendor's path, auth and required parameters, and the Microlink adapter live against api.microlink.io. The *_BASE_URL variables exist so you can point an adapter at a mock or a staging host. The script exits with code 1 if any request failed, so you can run it in CI.

Each cold request uses a credit, a unit or browser time with that vendor, so a run of 20 URLs × 3 runs × 2 phases is 120 requests per provider. On RenderScreenshot, warm hits sent with cache.available cost 0 credits (caching docs).

The vendors, one by one

RenderScreenshot

What it is. Our screenshot API. Rails handles auth, validation, caching and billing; a Cloudflare Worker drives Cloudflare Browser Rendering, and finished images go to R2 behind a CDN.

Strengths. Ten presets (og_card 1200×630, twitter_card 1200×600, twitter_card_large 1200×628, full_page, pdf_document, mobile and others). Three auth methods: Bearer header, api_key query parameter, and signed URLs that must expire within 30 days. Every successful image response carries X-Cache-URL, a public CDN link with no key in it. Cache TTL from 1 hour to 30 days, plus endpoints to delete or bulk-purge cached images. A batch endpoint, webhooks, bring-your-own S3 storage, PDF output, and SDKs for Node, Python, Ruby, PHP, Java and Go.

Weaknesses. No self-serve plan above 60,000 credits a month. No video output and no MCP server. Not the cheapest at 10,000 shots. Chromium only. No published benchmark numbers yet (see above).

Price. Free: 150 credits every month, no credit card. Starter $10 (2,500), Growth $40 (12,000), Scale $100 (60,000). One credit per fresh render.

curl -s -o shot.png \
  -H "Authorization: Bearer $RS_API_KEY" \
  "https://api.renderscreenshot.com/v1/screenshot?url=https%3A%2F%2Fexample.com&preset=og_card"

ScreenshotOne

What it is. A hosted screenshot API with one of the longest option lists in the market: viewport and device presets, several full-page algorithms, blocking, geolocation, metadata, storage upload, async jobs with webhooks, PDF, animated screenshots and an MCP server.

Strengths. Breadth of options. Failed renders and cache hits do not count toward quota, per its pricing FAQ. Caching returns a direct link in the x-screenshotone-cache-url header. Signed links. Extra usage at a per-screenshot price instead of a hard stop.

Weaknesses. The default cache TTL is 4 hours, and the docs say caching "is not intended to be used in CDN-like scenarios". I found no endpoint to purge one cached image; you change cache_key instead. IP location, scrolling screenshots and video start on the Growth plan.

Price. Free 100 a month. Basic $17 (2,000, then $0.009 each), Growth $79 (10,000, then $0.006), Scale $259 (50,000, then $0.004). Monthly billing; annual billing gives 2 months free.

curl -s -o shot.png \
  "https://api.screenshotone.com/take?access_key=$SCREENSHOTONE_ACCESS_KEY&url=https%3A%2F%2Fexample.com&viewport_width=1280&viewport_height=800&format=png"

More detail: ScreenshotOne alternative.

Urlbox

What it is. A long-running screenshot API with two ways in: render links (a URL you can put in an <img> tag, authenticated with an HMAC-SHA256 token of the query string) and a REST API (POST /v1/render/sync or /async) that answers with JSON.

Strengths. Render links are cached for 30 days by default and do not expire, which suits static sites. Output formats include PNG, JPEG, WebP, PDF, SVG, MP4, WebM and Markdown. Retina, dark mode, ad and cookie-banner hiding, click-to-accept, LLM extraction options and S3-compatible storage.

Weaknesses. No free tier, only a 7-day trial. The REST sync endpoint is not cached or de-duplicated, so each call is a billed render. One render is counted per 30 seconds of render time and per 5 MB of output, and managed proxies multiply the count (2× to 50×), so the plan number is not the screenshot number on heavy pages. The Lo-Fi plan is limited to low-quality thumbnails of third-party sites.

Price. Lo-Fi $19 (2,000), Hi-Fi $49 (5,000), Ultra $99 (15,000), Business $495 base plus $3 per 1,000.

curl -s -X POST https://api.urlbox.com/v1/render/sync \
  -H "Authorization: Bearer $URLBOX_SECRET" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","width":1280,"height":800,"format":"png"}'

More detail: Urlbox alternative.

ApiFlash

What it is. A small, focused screenshot API on Chrome and AWS Lambda. One endpoint, /v1/urltoimage, takes GET query parameters or POST form data.

Strengths. The lowest paid entry price on this list. Simple parameters (full_page, scroll_page, wait_until, no_ads, no_cookie_banners, no_tracking), S3 export, and cached results (default TTL one day, up to 30 days) that do not count toward quota. Quota headers on every response.

Weaknesses. The access key goes in the query string or form body, and there is no signed-link scheme, so you cannot put a request URL in public HTML. Defaults differ from most APIs (1920×1080, JPEG). Device scale factor is 1 or 2 only. IP geolocation is for custom enterprise plans.

Price. Free 100. Lite $7 (1,000), Medium $35 (10,000), Large $180 (100,000).

curl -s -o shot.png \
  "https://api.apiflash.com/v1/urltoimage?access_key=$APIFLASH_ACCESS_KEY&url=https%3A%2F%2Fexample.com&format=png&width=1280&height=800"

More detail: ApiFlash alternative.

ScreenshotAPI.net

What it is. A query-string screenshot API at shot.screenshotapi.net/screenshot with a token parameter.

Strengths. Low list prices at every volume, unlimited team members on all plans, and scrolling screenshots plus custom storage buckets from the Startup plan.

Weaknesses. I could verify less of this API than the others: the docs pages I fetched show the basic request and little else, so most checklist rows stay unscored. The pricing page shows reduced prices next to higher struck-through ones; confirm which one you will pay.

Price. 7-day trial with 100 screenshots. Essential $9 (1,000), Startup $29 (10,000), Business $175 (100,000), as displayed.

curl -s -o shot.png \
  "https://shot.screenshotapi.net/screenshot?token=$SCREENSHOTAPI_TOKEN&url=https%3A%2F%2Fexample.com&output=image&file_type=png"

Screenshot Machine

What it is. One of the oldest screenshot APIs (the footer says 2012). A single GET endpoint with key, url, dimension (for example 1366x768 or 1366xfull), device, format and cacheLimit.

Strengths. Cheap at volume, cached screenshots for 14 days that are not billed, unlimited impressions from cache, and overage billed in whole thousands rounded down, so 999 extra shots cost nothing.

Weaknesses. Output formats are JPG, PNG and GIF; WebP is not listed. The optional request hash is MD5 of the URL plus a secret phrase, with no expiry. Fewer capture controls than the newer APIs.

Price. Free 100. Basic €9 (2,500, then €0.004), Pro €59 (20,000, then €0.003), Enterprise €99 (50,000, then €0.002). Prices in EUR.

curl -s -o shot.png -G https://api.screenshotmachine.com \
  --data-urlencode "key=$SCREENSHOTMACHINE_KEY" --data-urlencode "url=https://example.com" \
  --data-urlencode "dimension=1280x800" --data-urlencode "format=png"

What it is. A browser API that returns structured data about a URL (metadata, logo, palette, Lighthouse, Markdown) and, with screenshot=true, a screenshot. The keyless endpoint api.microlink.io works for small tests; the paid endpoint is pro.microlink.io with an x-api-key header.

Strengths. Metadata and image in one call. Cache hits do not count against quota. No overage fees: requests pause at 100% of quota until you upgrade. It was the only vendor here I could test live without signing up; 4 of 4 requests succeeded (see the method box).

Weaknesses. The default response is JSON that points at a CDN file, so you need a second request for the bytes, or embed=screenshot.url. The default device scale factor is 2, which doubles pixel dimensions. The entry paid tier is 46,000 requests, which is expensive per shot at low volume.

Price. Free 100 requests a month. Pro tiers in EUR from the page's price data: €39 (46,000), €72 (84,000), €120 (140,000). The page displayed $49 for the first tier to me.

curl -s -o shot.png -G https://api.microlink.io/ \
  --data-urlencode "url=https://example.com" -d screenshot=true \
  -d viewport.deviceScaleFactor=1 -d embed=screenshot.url

More detail: Microlink alternative.

Browserless

What it is. Hosted headless browsers. You point Puppeteer or Playwright at a WebSocket URL instead of launching Chrome, or you call REST endpoints such as POST /screenshot?token=... with Puppeteer-style options.

Strengths. Full browser control for logins, multi-step flows and scraping, with stealth, proxies and captcha solving. Chrome, Firefox and WebKit. Self-hosting licences. Your existing Puppeteer code mostly keeps working.

Weaknesses. You still write and maintain wait logic, retries and caching. Billing is in units of up to 30 seconds of browser time, and a reconnect is a new unit, so cost per screenshot depends on your code. No image cache or public link.

Price. Free (1,000 units), Prototyping $25 (20,000 units), Starter $140 (180,000), Scale $350 (500,000), at the annual-billing rates the page shows by default.

curl -s -o shot.png -X POST "https://production-sfo.browserless.io/screenshot?token=$BROWSERLESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","options":{"type":"png"}}'

More detail: Browserless alternative.

Cloudflare Browser Run (Browser Rendering)

What it is. Headless Chromium on Cloudflare's network. Cloudflare's docs now call the product "Browser Run"; most people still search for Browser Rendering. You can call Quick Actions over REST (/client/v4/accounts/{account_id}/browser-run/screenshot) or from a Worker binding, or drive a browser session with Puppeteer or Playwright.

Strengths. Very low cost per screenshot at volume, because it bills browser time. No servers to run. Good fit if you already deploy Workers. RenderScreenshot itself runs on it, so we know it works at production volume.

Weaknesses. You build caching, auth for public links, retries and output storage yourself. The free plan allows 10 browser minutes a day. Limits apply per account (concurrent browsers, new browsers per second, a 60-second browser timeout). Chromium only.

Price. Workers Paid ($5 a month minimum) includes 10 browser hours; then $0.09 per hour. At 2 to 10 seconds of browser time per shot, 10,000 shots cost about $5 to $7 a month.

curl -s -o shot.png -X POST \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/browser-run/screenshot" \
  -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}'

More detail: Cloudflare Browser Rendering alternative.

ScrapingBee

What it is. A web scraping API with rotating proxies. screenshot=true, screenshot_full_page and screenshot_selector turn a scrape into a capture.

Strengths. Proxies and JavaScript scenarios (click, scroll, wait) are built in, and credits are cheap in bulk.

Weaknesses. It is a scraping product first: no image cache, no public links, and premium proxies cost extra credits. JavaScript rendering, which a screenshot needs, costs 5 credits per request.

Price. Hobby $19 (75,000 credits, so 15,000 rendered shots), Freelance $49 (250,000), Startup $99 (1,000,000), Business $249 (3,000,000). 1,000 free trial credits.

curl -s -o shot.png -G https://app.scrapingbee.com/api/v1/ \
  --data-urlencode "api_key=$SCRAPINGBEE_API_KEY" --data-urlencode "url=https://example.com" \
  -d screenshot=true

Self-hosted Puppeteer or Playwright (baseline)

What it is. Chrome on your own servers, driven by your code. This is the baseline every API competes with.

Strengths. No per-shot fee, any automation you can write, and data that never leaves your network. In my local runs, a reused browser returned a 1280×800 PNG of example.com in about 190 to 260 ms (p50).

Weaknesses. Launching a fresh browser per shot cost 0.5 to 1 second (p50) before any network time. The summed resident memory of Puppeteer's Chrome process tree was 761 to 776 MB on macOS (this sum counts shared pages more than once, so treat it as an upper bound). You own fonts, zombie processes, timeouts, upgrades and scaling. Our posts on Puppeteer timeouts and memory leaks and scaling Puppeteer screenshots cover that work. See also the Puppeteer alternative and Playwright alternative pages.

Which one should you pick?

  1. Write down your volume, formats and whether the image must appear in public HTML.
  2. If the image goes into a public page or an og:image tag, shortlist services with signed links: RenderScreenshot, ScreenshotOne, Urlbox.
  3. If you need logins, clicks and multi-step flows, shortlist Browserless or Cloudflare Browser Run, or keep Puppeteer.
  4. If the lowest monthly bill at high volume decides it, read the pricing comparison first.
  5. Run compare.mjs on 20 or more of your own URLs and look at every image, not only the numbers.

Reproduce this comparison: create a free RenderScreenshot key (150 credits a month, no credit card), set RS_API_KEY, and run the script next to the vendors you already use. The quick start has the first request.

Changelog

  • 2026-10-15: Rewrote the page. Added ScreenshotOne, ApiFlash, Screenshot Machine, Microlink, Cloudflare Browser Run and ScrapingBee (10 services plus self-hosted). Prices re-checked on 2026-10-14. Added the scored checklist, the compare.mjs script, the method box, the Microlink live run and the local Puppeteer and Playwright baselines. Fixed our own errors: the twitter_card preset is 1200×600 (twitter_card_large is 1200×628), the Node SDK uses new Client() and TakeOptions, our free plan is 150 credits a month, and we have six official SDKs, not one.
  • 2026-02-10: First version (RenderScreenshot, ScreenshotAPI.net, Urlbox, Browserless, self-hosted).

Related in this topic