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.
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:imagetags: RenderScreenshot. Theog_cardpreset (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
rsbenchquery 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"
Microlink
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?
- Write down your volume, formats and whether the image must appear in public HTML.
- If the image goes into a public page or an
og:imagetag, shortlist services with signed links: RenderScreenshot, ScreenshotOne, Urlbox. - If you need logins, clicks and multi-step flows, shortlist Browserless or Cloudflare Browser Run, or keep Puppeteer.
- If the lowest monthly bill at high volume decides it, read the pricing comparison first.
- Run
compare.mjson 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.mjsscript, the method box, the Microlink live run and the local Puppeteer and Playwright baselines. Fixed our own errors: thetwitter_cardpreset is 1200×600 (twitter_card_largeis 1200×628), the Node SDK usesnew Client()andTakeOptions, 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 guides
- Screenshot API pricing comparison: every plan at 1,000, 10,000 and 100,000 shots, with a calculator.
- How to choose a screenshot API: weight the criteria and score your shortlist.
- All alternative pages: one page per tool, with parameter mapping and migration code.
- Puppeteer vs Playwright for screenshots: if you decide to self-host.
- ScreenshotOne vs Urlbox: the two widest option lists side by side.
- Best headless browser APIs: hosted browsers you drive with your own Puppeteer or Playwright code.
Related in this topic
- Screenshot API Alternatives Alternatives to screenshot APIs, OG image tools, PDF APIs and headless browser libraries, with honest notes on when each option is the better fit.
- Screenshot API Pricing Comparison: 10 Vendors (2026) Screenshot API pricing compared at 1,000, 10,000 and 100,000 shots a month for 10 vendors, with cost per 1,000, billing-unit traps and a calculator.
- ApiFlash Alternative: Parameter Map and Migration An ApiFlash alternative with signed links and presets: full parameter map, ApiFlash pricing at three volumes, and Node, Python and Ruby migration code.
- Browserless Alternative for Screenshots A Browserless alternative for screenshots: browser infrastructure vs a screenshot API, cost per screenshot, and /screenshot migration code with a fallback.
- Microlink Alternative for Screenshots: Timings and Switch A Microlink alternative for screenshots: live Microlink timings, its 2x pixel-ratio default, a parameter map, pricing and migration code in 3 languages.
- Playwright Alternative for Screenshots (Node and Python) A Playwright alternative for production screenshots: measured Playwright timings, Node and Python code before and after, and why to keep it for tests.