Disclosure: I build RenderScreenshot, a third screenshot API, so I compete with both. I have not run paid accounts on either service for this page. Every fact below comes from their documentation and pricing pages as read on 2026-10-14 and 2026-10-15, with links, and the test script at the end is untested against live keys. Where one of them is the better choice, I say so.
Short answer: ApiFlash is the cheaper, simpler API: one endpoint, sensible defaults, caching on by default, and the lowest list price at every volume we checked. ScreenshotOne has the larger feature set: signed links for public embedding, async jobs with webhooks, S3 upload, HTML and Markdown input, PDF output, more blocking and wait controls, and metadata extraction. If you need screenshots of public pages from server code, start with ApiFlash's price. If you embed screenshot URLs in public HTML, render PDFs, or need fine control over waiting and blocking, ScreenshotOne earns its higher price.
This page is one of our screenshot API comparisons. If ApiFlash is on your shortlist next to Urlbox, see Urlbox vs ApiFlash.
The two APIs in one table
| ScreenshotOne | ApiFlash | |
|---|---|---|
| Endpoint | GET or POST https://api.screenshotone.com/take |
GET or POST https://api.apiflash.com/v1/urltoimage (POST takes form data) |
| Auth | access_key parameter or X-Access-Key header |
access_key parameter |
| Public embedding | Signed links: HMAC-SHA256 of the query string, GET only; can be required for every request | No signing; the key is in the URL |
| Inputs | URL, HTML, Markdown | URL |
| Outputs | Images (JPEG default, PNG, WebP and more), PDF, HTML, Markdown | JPEG (default), PNG, WebP |
| Response | Bytes by default; response_type=json or empty |
Bytes by default; response_type=json returns file URLs |
| Async + webhooks | async=true with webhook_url |
No |
| Upload to your storage | store=true to S3-compatible storage |
S3 export (listed on all plans) |
| Infrastructure (vendor's description) | Not stated in the pages we read | Chrome on AWS Lambda |
Sources: ScreenshotOne options, ScreenshotOne signed links, ApiFlash documentation.
Defaults that make the first comparison unfair
If you call both with only a URL, you are not comparing like with like.
| Setting | ScreenshotOne default | ApiFlash default |
|---|---|---|
| Viewport | 1280 wide (viewport_width) |
1920x1080 |
| Format | jpg |
jpeg |
| Wait condition | load |
network_idle |
| Full-page lazy loading | full_page_scroll turns on with full_page=true |
Separate scroll_page=true |
| Cache | Off until cache=true; then 4 hours by default |
On; 24 hours by default (ttl=86400) |
| Timeout | 60 s default, 90 s max | wait_until_timeout 30 s max, then captures whatever loaded |
The cache row is the one that surprises people in testing. With ApiFlash, your second request for the same URL and parameters returns the first image for 24 hours unless you pass fresh=true. With ScreenshotOne, every request renders unless you opt into caching.
The timeout rows differ in kind, too. ApiFlash's wait_until_timeout captures the page as it is when the time runs out. A ScreenshotOne timeout aborts the request. One gives you a possibly incomplete image; the other gives you an error you can retry.
Parameter mapping
For a 1280x800 PNG with ads and cookie banners blocked, waiting for the network to settle:
| What you want | ScreenshotOne | ApiFlash |
|---|---|---|
| Target page | url |
url |
| Viewport | viewport_width=1280&viewport_height=800 |
width=1280&height=800 |
| Pixel density | device_scale_factor=2 (1 to 5) |
scale_factor=2 (1 or 2) |
| Format | format=png |
format=png |
| Quality | image_quality=80 |
quality=80 |
| Full page | full_page=true |
full_page=true (plus scroll_page=true for lazy images) |
| One element | selector=.hero |
element=.hero |
| Wait for network | wait_until=networkidle2 (or networkidle0) |
wait_until=network_idle |
| Wait for an element | wait_for_selector=.ready |
wait_for=.ready |
| Extra delay | delay (seconds) |
delay (seconds, max 10) |
| Block ads | block_ads=true |
no_ads=true |
| Block cookie banners | block_cookie_banners=true |
no_cookie_banners=true |
| Block trackers | block_trackers=true |
no_tracking=true |
| Hide elements / inject CSS | hide_selectors, styles |
css |
| Inject JavaScript | scripts |
js |
| Transparent background | omit_background=true (PNG only) |
transparent=true (PNG only) |
| Country of the request IP | ip_country_code=gb |
ip_location=gb (custom enterprise plans) |
| Fail on bad status | fail_if_* options |
fail_on_status=400,404,500-511 |
| Fresh render | omit cache |
fresh=true |
Quota, caching and rate limits
What counts against your quota.
- ScreenshotOne: only successful renders that were not served from cache. Its pricing FAQ says screenshots with visual issues still count, and that it reviews reported defects for refunds.
- ApiFlash: requests served from cache do not count. Repeated failing calls with identical parameters are limited to 5 per hour.
Rate limits.
- ScreenshotOne: requests per minute by plan (40, 80 and 150 on Basic, Growth and Scale). Its concurrency docs explain that each accepted request spends one start from a one-minute bucket; when the bucket is empty you get
concurrency_limit_reached. - ApiFlash: a leaky bucket of 20 requests per second with a burst of 400. Above that, requests are delayed, then rejected with
429once the burst is full.
For a batch job, that difference is large. ApiFlash will accept a burst of hundreds of URLs and pace them for you. ScreenshotOne's Basic plan starts 40 per minute, so you need your own queue.
Quota visibility. ApiFlash returns X-Quota-Limit, X-Quota-Remaining and X-Quota-Reset headers on successful renders and has a /quota endpoint. ScreenshotOne has a usage endpoint and email alerts at 90% and 100%.
Price at three volumes
List prices as shown on the vendors' pages on 2026-10-14 (ScreenshotOne) and 2026-10-15 (ApiFlash), monthly billing:
| Screenshots per month | ScreenshotOne | ApiFlash |
|---|---|---|
| 1,000 | Basic, $17 (2,000 included) | Lite, $7 (1,000) |
| 10,000 | Growth, $79 (10,000) | Medium, $35 (10,000) |
| 100,000 | Scale, $259 for 50,000 + 50,000 extra at $0.004 = $459 | Large, $180 (100,000) |
Both have a free tier of 100 screenshots a month with no credit card. ScreenshotOne offers two months free on annual billing. For comparison, RenderScreenshot lists $10 for 2,500, $40 for 12,000 and $100 for 60,000 credits (pricing), with cached responses free when you request them.
At 100,000 a month, ApiFlash costs less than half of ScreenshotOne's list price. At that volume, the question is whether the ScreenshotOne features you would use are worth about $280 a month.
Test both on your own URLs
This Node 22 script sends the same request to both APIs with every default overridden, checks the image signature, and prints time, size and quota headers. Run it twice per URL: the first run with fresh renders, the second with caching on, to see each service's cache path. It has not been run against live keys for this page.
// Same capture on ScreenshotOne and ApiFlash with matched settings. // Usage: node compare.mjs https://example.com [--cached] // Env: SCREENSHOTONE_ACCESS_KEY, APIFLASH_ACCESS_KEY import { writeFile } from 'node:fs/promises'; const target = process.argv[2] ?? 'https://example.com'; const cached = process.argv.includes('--cached'); const requests = { screenshotone: { url: 'https://api.screenshotone.com/take', headers: { 'X-Access-Key': process.env.SCREENSHOTONE_ACCESS_KEY }, params: { url: target, format: 'png', viewport_width: '1280', viewport_height: '800', device_scale_factor: '1', wait_until: 'networkidle2', block_cookie_banners: 'true', ...(cached ? { cache: 'true', cache_ttl: '14400' } : {}), }, }, apiflash: { url: 'https://api.apiflash.com/v1/urltoimage', headers: {}, params: { access_key: process.env.APIFLASH_ACCESS_KEY, // ApiFlash only accepts the key as a parameter url: target, format: 'png', width: '1280', height: '800', scale_factor: '1', wait_until: 'network_idle', no_cookie_banners: 'true', ...(cached ? { ttl: '14400' } : { fresh: 'true' }), }, }, }; const PNG = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); for (const [name, req] of Object.entries(requests)) { const u = new URL(req.url); u.search = new URLSearchParams(req.params).toString(); const t0 = performance.now(); try { const res = await fetch(u, { headers: req.headers, signal: AbortSignal.timeout(120_000) }); const body = Buffer.from(await res.arrayBuffer()); const ms = (performance.now() - t0).toFixed(0); if (!res.ok) throw new Error(`HTTP ${res.status}: ${body.toString('utf8', 0, 300)}`); if (!body.subarray(0, 8).equals(PNG)) throw new Error(`not a PNG (${res.headers.get('content-type')})`); await writeFile(`${name}${cached ? '-cached' : ''}.png`, body); const quota = res.headers.get('x-quota-remaining'); console.log(`${name}: ${ms} ms, ${body.length} bytes${quota ? `, quota left ${quota}` : ''}`); } catch (err) { console.error(`${name}: ${err.message}`); process.exitCode = 1; } }
Do not log the full ApiFlash URL: it contains your key. For each URL, run the script without --cached at least five times for render latency, then with --cached twice and read the second result for cache latency. Compare the images at full size, not just the numbers.
Where each one is the better choice
ApiFlash fits when:
- You capture public pages from your own servers and never expose request URLs.
- Cost per screenshot is the main criterion.
- You send bursts and want the API to pace them (20 per second, burst of 400).
- You want caching on by default with a long TTL.
ScreenshotOne fits when:
- You put screenshot URLs into public HTML and need signed links.
- You render HTML or Markdown, or produce PDFs, in the same integration.
- You want async jobs with webhooks and upload to your own S3 bucket.
- You need more control: wait for multiple events, detailed blocking, metadata extraction, failure conditions.
A third option
If you want signed URLs for <img> tags and a cache you can serve publicly, RenderScreenshot returns an X-Cache-URL CDN link with every response and supports signed URLs with a required expiry of up to 30 days:
curl -o example.png -D headers.txt \ -H "Authorization: Bearer $RS_API_KEY" \ "https://api.renderscreenshot.com/v1/screenshot?url=https%3A%2F%2Fexample.com&width=1280&height=800&format=png&block_cookies=true" grep -i x-cache-url headers.txt
Add it as a third entry in the script above to compare all three on the same URLs. Get a free API key to run it.
Last verified: 2026-10-15 (docs and pricing pages). Measured results pending.