Scrapfly Alternative for Screenshots Only

A Scrapfly alternative for teams that only need screenshots: credit cost per image, Screenshot API parameters mapped, and tested Node, Python and Ruby code.

By Simon Chiu · Updated

Disclosure: I build RenderScreenshot, a screenshot-only API. Scrapfly is a scraping platform that also takes screenshots, and for hard-to-reach sites its unblocking is a real advantage we do not match. This page is about the screenshot part only.

Scrapfly is built for web scraping: one credit pool shared by its Web Scraping, Screenshot, Extraction, Crawler and Cloud Browser APIs, with proxies in many countries and automatic anti-bot handling. Its Screenshot API is a simplified product inside that platform. If screenshots are all you use Scrapfly for, you pay scraping-platform prices for an image: 60 credits per capture. This page shows what that costs per image at each plan, maps the Screenshot API parameters to ours, and gives you code to move the screenshot calls while you keep Scrapfly for scraping. It is a spoke of our best screenshot API comparison.

Last verified: October 15, 2026, against Scrapfly Screenshot API docs, Screenshot API billing and Scrapfly pricing.

Should you move screenshots off Scrapfly?

Keep screenshots on Scrapfly if Move screenshots to RenderScreenshot if
Your targets block normal headless browsers and you need Scrapfly's unblocking Your targets are your own pages, customer sites or public pages that load normally
You already spend the credits on scraping and screenshots are a rounding error Screenshots are most of your Scrapfly credit spend
You need proxies from many specific countries, weighted or excluded network.country routing or your own proxy covers your geo needs
You test accessibility with vision_deficiency simulation You want public CDN links, signed URLs and social-card presets
You capture by XPath CSS selectors are fine

Cost per screenshot

Scrapfly bills credits, and a screenshot costs 60 of them (plus 1 credit per 100 KB over 4 MB). Scrapfly's billing page converts its plans into screenshots for you:

Scrapfly plan (monthly) Credits Screenshots at 60 credits Price per 1,000 screenshots
Free (one-time on signup) 1,000 about 16 n/a
Discovery, $30 200,000 about 3,333 about $9.00
Pro, $100 1,000,000 about 16,667 about $6.00
Startup, $250 2,500,000 about 41,667 about $6.00
Enterprise, $500 5,500,000 about 91,667 about $5.45

The last column is our arithmetic from the listed prices. Scrapfly does not charge for failed requests, which matters if many of your targets fail.

Pricing at three volumes

Checked October 15, 2026. RenderScreenshot uses one credit per screenshot.

Monthly screenshots Scrapfly (screenshot credits only) RenderScreenshot
1,000 (60,000 credits) $30 (Discovery) $10 (Starter, 2,500 credits)
10,000 (600,000 credits) $100 (Pro) $40 (Growth, 12,000 credits)
100,000 (6,000,000 credits) about $560: Enterprise ($500, 5.5M credits) + 500,000 extra credits at $1.20 per 10,000 above our largest listed plan (Scale, $100 for 60,000 credits)
Free 1,000 credits once, no card 150 credits every month, no card

The 100,000 line uses the pay-as-you-go rate on Scrapfly's pricing page; Scrapfly also offers custom contracts at that size. If you share the credit pool with scraping, your real screenshot cost is whatever those credits would otherwise buy you.

Parameter map

Scrapfly's Screenshot API is controlled entirely by query parameters on GET https://api.scrapfly.io/screenshot. Here is each one and where it goes:

Scrapfly RenderScreenshot Notes
key (query) Authorization: Bearer rs_live_...
url url
format = jpg (default), png, webp, gif output.format = png (default), jpeg, webp, avif, pdf No GIF on our side
capture=viewport (default) capture.mode: viewport (default)
capture=fullpage capture.mode: full_page
capture=<CSS selector> capture.mode: element + capture.selector
capture=<XPath> not supported Rewrite as a CSS selector
resolution=1920x1080 (default) viewport.width / viewport.height (default 1280×800) Set 1920×1080 to keep the same framing
rendering_wait (ms, default 1000) wait.delay (ms, default 0)
wait_for_selector (CSS or XPath) wait.for_selector (CSS)
timeout (ms, 60,000 to 120,000) wait.timeout (seconds, up to 60)
options=block_banners block.cookie_banners (+ block.ads)
options=dark_mode browser.dark_mode
options=print_media_format browser.media: print
auto_scroll=true capture.scroll.simulate: true (or the full_page preset)
js (base64, max 16 KB) page.scripts (plain strings or {url}) Decode before you send
country network.country Single country code
cache, cache_ttl (default 86,400), cache_clear cache.enabled (on by default), cache.ttl, cache.refresh Our TTL minimum is 1 hour
webhook_name webhook.url A URL per request instead of a named dashboard webhook
vision_deficiency not supported

Two defaults move your output if you leave them alone: format (jpg against png) and viewport (1920×1080 against 1280×800). The translation function below sets both from the Scrapfly URL, so a request without those parameters still produces the same kind of image.

Storage: the header your code reads changes

Scrapfly returns the image and an X-Scrapfly-Screenshot-Url header that points to its stored copy. That stored URL still needs your key (?key=...) to fetch, and its retention follows your plan's log retention (1 to 4 weeks). It is a retrieval link, not something you can put in public HTML.

Our equivalent is X-Cache-URL: a CDN link with no key in it, valid until the cache entry expires (24 hours by default, up to 30 days with cache.ttl). You can put it in an <img> tag. If your code stored Scrapfly's header to fetch the image later, store ours to display it directly (caching docs).

Measuring before you switch

This page has no measured latency or side-by-side renders yet; we have not run Scrapfly through our benchmark. A fair comparison needs to separate the two things Scrapfly does: rendering, and unblocking. Run your screenshot URLs through both services with the same resolution and format. On sites that do not block bots, compare speed and images. On sites that do block them, expect Scrapfly to succeed where a plain renderer gets a challenge page, and keep those URLs on Scrapfly.

Migration code

Node: translate an existing Scrapfly URL

Most Scrapfly integrations build the query string in one place. This function takes that URL and returns the equivalent TakeOptions, so you can switch call sites one at a time.

// translate.mjs: turn an existing Scrapfly Screenshot API URL into a RenderScreenshot call.
// Node 22+, npm i renderscreenshot
// Usage: RS_API_KEY=rs_live_... node translate.mjs "https://api.scrapfly.io/screenshot?url=...&capture=fullpage"
import { Client, TakeOptions } from 'renderscreenshot';
import { writeFile } from 'node:fs/promises';

export function fromScrapfly(scrapflyUrl) {
  const q = new URL(scrapflyUrl).searchParams;
  let o = TakeOptions.url(q.get('url'));

  // resolution=1920x1080 is Scrapfly's default; RenderScreenshot defaults to 1280x800.
  const [w, h] = (q.get('resolution') ?? '1920x1080').split('x').map(Number);
  o = o.width(w).height(h);

  // capture=viewport | fullpage | <CSS selector>. (XPath targets need a CSS rewrite.)
  const capture = q.get('capture') ?? 'viewport';
  if (capture === 'fullpage') o = o.fullPage();
  else if (capture !== 'viewport') {
    if (capture.startsWith('/')) throw new Error(`XPath capture "${capture}" needs a CSS selector`);
    o = o.element(capture);
  }

  // format defaults to jpg on Scrapfly and png here; keep what your code expects.
  const fmt = q.get('format') ?? 'jpg';
  o = o.format(fmt === 'jpg' ? 'jpeg' : fmt);

  const flags = (q.get('options') ?? '').split(',').filter(Boolean);
  if (flags.includes('block_banners')) o = o.blockCookieBanners().blockAds();
  if (flags.includes('dark_mode')) o = o.darkMode();
  if (flags.includes('print_media_format')) o = o.mediaType('print');

  // Scrapfly waits 1000 ms after load by default (rendering_wait).
  o = o.delay(Number(q.get('rendering_wait') ?? 1000));
  if (q.get('wait_for_selector')) o = o.waitForSelector(q.get('wait_for_selector'));
  if (q.get('cache_ttl')) o = o.cacheTtl(Math.max(3600, Number(q.get('cache_ttl'))));
  // auto_scroll, js (base64), country and vision_deficiency are not mapped here:
  // see the mapping table for what to do with each one.
  return o;
}

try {
  const source = process.argv[2] ?? 'https://api.scrapfly.io/screenshot?url=https%3A%2F%2Fexample.com&capture=fullpage&options=block_banners,dark_mode&format=png';
  const client = new Client(process.env.RS_API_KEY, process.env.RS_BASE_URL ? { baseUrl: process.env.RS_BASE_URL } : {});
  const image = await client.take(fromScrapfly(source));
  await writeFile('translated.png', image);
  console.log(`saved ${image.length} bytes`);
} catch (err) {
  console.error(err.message);
  process.exitCode = 1;
}
"""storage_url.py: Scrapfly returns X-Scrapfly-Screenshot-Url (needs ?key= to fetch);
RenderScreenshot returns X-Cache-URL (public CDN link, no key). Store the right one.

Python 3.10+, standard library only.
Usage: RS_API_KEY=rs_live_... python3 storage_url.py https://example.com
"""
import json
import os
import sys
import urllib.error
import urllib.request

RS = os.environ.get("RS_BASE_URL", "https://api.renderscreenshot.com")


def capture_and_get_cdn_url(url: str) -> tuple[bytes, str]:
    body = {"url": url, "preset": "og_card", "output": {"format": "webp"}, "cache": {"ttl": 7 * 86400}}
    req = urllib.request.Request(
        f"{RS}/v1/screenshot", data=json.dumps(body).encode(), method="POST",
        headers={"Authorization": f"Bearer {os.environ['RS_API_KEY']}", "Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req, timeout=60) as res:
        # Public link, valid until the cache entry expires (here: 7 days).
        return res.read(), res.headers["X-Cache-URL"]


if __name__ == "__main__":
    try:
        image, cdn_url = capture_and_get_cdn_url(sys.argv[1] if len(sys.argv) > 1 else "https://example.com")
    except urllib.error.HTTPError as err:
        print(f"HTTP {err.code}: {err.read()[:200]!r}", file=sys.stderr)
        sys.exit(1)
    print(f"{len(image)} bytes; embed {cdn_url}")

The example uses the og_card preset (1200×630) with WebP output, a common reason teams send screenshots through Scrapfly in the first place: link previews and social images of their own pages.

Ruby: screenshot calls only

# screenshot_only.rb: the screenshot part of a Scrapfly integration, moved to RenderScreenshot.
# Keep Scrapfly for scraping; send only the image work here.
# gem install renderscreenshot
require 'renderscreenshot'

client = RenderScreenshot.client(ENV.fetch('RS_API_KEY'), base_url: ENV['RS_BASE_URL'])

url = ARGV.fetch(0, 'https://example.com')
options = RenderScreenshot::TakeOptions.url(url)
                                       .width(1920).height(1080) # Scrapfly's default resolution
                                       .full_page
                                       .block_cookie_banners      # options=block_banners
                                       .wait_for('networkidle')
                                       .format('webp')

begin
  File.binwrite('page.webp', client.take(options))
  puts 'saved page.webp'
rescue RenderScreenshot::Error => e
  warn "#{e.code} (retryable: #{e.retryable?}) request #{e.request_id}"
  exit 1
end

All three tested on October 15, 2026 against a local mock that records request bodies and returns the documented headers: Node 22.18.0 with [email protected], Python 3.14.8, Ruby 4.0.7 with the renderscreenshot gem 1.0.0. Not run against production APIs.

Rollout

  1. Split by purpose. Leave every scraping call on Scrapfly. List the call sites that only want an image.
  2. Classify targets. For each screenshot target, check whether it needs Scrapfly's unblocking. Your own pages and most customer pages do not.
  3. Translate and shadow. Use fromScrapfly on the existing URLs, render with both for a day and compare images.
  4. Switch the easy targets, keep the blocked ones on Scrapfly, and watch your Scrapfly credit spend drop.

When Scrapfly is the better choice

Scrapfly is the better choice for screenshots of sites that actively block automated browsers: its unblocking, residential proxies and country targeting exist for exactly that, and we do not claim to match them. It is also simpler to keep one vendor if screenshots are a small share of a large scraping budget. For a wider view of scraping-first services, see our ScrapingBee alternative page.

If most of your Scrapfly credits go to images of pages that load normally, create a free key, run fromScrapfly on five of your existing URLs, and compare the images.

Frequently asked questions

How many credits does a Scrapfly screenshot cost?
Scrapfly's Screenshot API documentation lists 60 API credits per screenshot, plus 1 credit for every 100 KB over 4 MB. Its pricing page says failed requests are not charged.
How many screenshots do Scrapfly's plans include?
Scrapfly's own billing page lists about 3,333 screenshots on Discovery ($30), 16,667 on Pro ($100), 41,667 on Startup ($250) and 91,667 on Enterprise ($500) per month, checked October 15, 2026.
Can I keep Scrapfly for scraping and move only screenshots?
Yes, and for most teams that is the right split. Scrapfly's strengths are anti-bot unblocking and residential proxies for scraping. If your screenshots are of your own pages, customer URLs or public sites that do not block you, a screenshot-only API costs less per image.
Does RenderScreenshot support XPath capture like Scrapfly?
No. Element capture takes a CSS selector. Rewrite XPath targets as CSS selectors when you migrate.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.