ScreenshotOne vs Urlbox: Parameters, Pricing, Test Script

ScreenshotOne vs Urlbox compared by a third vendor: option-by-option mapping, plan prices at 1k, 10k and 100k checked October 2026, and a script to test both.

By Simon Chiu · Updated

Disclosure: we build RenderScreenshot, a third screenshot API that competes with both ScreenshotOne and Urlbox. We are not neutral, so this page sticks to facts you can check: each vendor's own documentation and pricing page, read on 2026-10-15 and linked below, plus a test script you run with your own keys. We do not rate either product.

ScreenshotOne and Urlbox are both mature, developer-focused screenshot APIs on managed Chromium. Most comparisons between them argue about features both have. The differences that change a buying decision are fewer: how each one prices volume, how the plans gate features, what the API returns, and what the option names are when you write the integration. This page covers those. Our tool comparisons hub lists other pairings.

Decision table

If you need Look first at Source
The lowest paid entry price for third-party sites ScreenshotOne (Basic, $17 for 2,000) ScreenshotOne pricing
Cheap renders of your own pages only (OG images, thumbnails) Urlbox Lo-Fi ($19 for up to 2,000) Urlbox pricing
Billing only for successful, non-cached renders ScreenshotOne (states this in its pricing FAQ) ScreenshotOne pricing FAQ
Long render timeouts (up to 300 s) or no file-size cap Urlbox Ultra or Business Urlbox plan table
SVG or saved HTML output Urlbox lists both as format values Urlbox options
Scrolling videos and IP-location choice ScreenshotOne Growth or higher ScreenshotOne plan table
An uptime SLA on a self-serve plan Urlbox Business (99.95%) Urlbox plan table
A free tier that does not expire ScreenshotOne (100 screenshots a month) ScreenshotOne pricing
A trial with paid-plan features Urlbox (7-day trial, no credit card) Urlbox pricing

Price at three volumes

List prices as shown on each pricing page on 2026-10-15, monthly billing, USD, before tax. Urlbox prices plans on a volume slider; where a tier price was not shown on the page, we say so instead of guessing.

Screenshots per month ScreenshotOne Urlbox
1,000 Basic: $17 (2,000 included) Hi-Fi: $49 (up to 5,000). Lo-Fi: $19 (up to 2,000), own sites and thumbnails only
10,000 Growth: $79 (10,000 included) Ultra: $99 (up to 15,000). Hi-Fi above 5,000: price set on the slider
100,000 Scale $259 (50,000) + 50,000 extra at $0.004 = $459 Business: $495 base + $3 per 1,000 renders = $795 by its stated formula. Ultra above 15,000: price set on the slider

Two plan details matter more than the headline price:

  • ScreenshotOne gates by feature. Basic includes blocking, PDF, HTML rendering, caching, S3 upload, webhooks and signed links. Growth adds IP-location selection, scrolling screenshots and video. Scale adds GPU rendering and priority support.
  • Urlbox gates by target and limits. Lo-Fi is described as screenshots of websites you own, with low-quality third-party thumbnails. Hi-Fi covers "most" third-party sites, Ultra "all" with stealth request handling, and the plans differ in request timeout (30, 90, 300 s), file-size limit (2.5 MB, 10 MB, none) and requests per minute.

For reference, RenderScreenshot lists Starter at $10 (2,500 credits), Growth at $40 (12,000) and Scale at $100 (60,000), with 150 free credits every month; see /pricing.

Option mapping

ScreenshotOne names come from the parameters used across its documentation and examples; Urlbox names come from its options reference. We add our own names in the last column so you can see all three side by side.

Task ScreenshotOne Urlbox RenderScreenshot
Auth (server side) access_key query param Authorization: Bearer <secret> on /v1/render/sync Authorization: Bearer rs_live_...
Auth (public embed) Signed links (signature) Render links Signed URLs
Viewport viewport_width, viewport_height width, height viewport.width, viewport.height
Pixel density device_scale_factor retina (2x) viewport.scale (1 to 3)
Full page full_page full_page capture.mode: "full_page"
One element selector selector capture.selector
Format format format (png, jpeg, webp, avif, svg, pdf, html, mp4, webm, md) output.format (png, jpeg, webp, avif, pdf)
Quality image_quality quality (JPEG/WebP) output.quality
Transparent background omit_background transparent output.transparent
Wait for an event wait_until wait_until (domloaded, loaded, requestsfinished, mostrequestsfinished) wait.until (domcontentloaded, load, networkidle)
Wait for an element wait_for_selector wait_for wait.for_selector
Fixed delay delay delay (ms) wait.delay (ms)
Cookie banners block_cookie_banners hide_cookie_banners, click_accept block.cookie_banners
Ads, trackers, chats block_ads, block_trackers, block_chats block_ads, block_urls (patterns) block.ads, block.trackers, block.chat_widgets
Hide elements hide_selectors hide_selector page.hide
Click before capture click click page.click
Custom CSS / JS styles, scripts css, js page.styles, page.scripts
Dark mode dark_mode dark_mode browser.dark_mode
Print media media_type media browser.media
User agent user_agent user_agent browser.user_agent
Headers, cookies headers, cookies header, cookie network.headers, network.cookies
Cache TTL / fresh render cache, cache_ttl, cache_key ttl (max 2,592,000 s), force cache.ttl, cache.refresh
Upload to your bucket store, storage_path use_s3 storage.enabled, storage.path
Webhook webhook_url webhook_url webhook.url
Fail on missing content fail_if_content_contains fail_if_selector_missing options.fail_if_missing, options.fail_if_contains

Watch the wait semantics when you move between the two. Urlbox's requestsfinished means no more than 0 network connections for at least 500 ms and mostrequestsfinished allows 2, as its options page defines them. Those match Puppeteer's networkidle0 and networkidle2. Check ScreenshotOne's wait_until values in its docs before you map them one to one.

Response shapes

  • ScreenshotOne GET https://api.screenshotone.com/take?access_key=...&url=... returns the rendered file. response_type changes this (for example to JSON), and store uploads to your bucket.
  • Urlbox POST https://api.urlbox.com/v1/render/sync returns JSON with renderUrl and size. You download the file from renderUrl. The async endpoint and webhooks cover long renders. Render links (/v1/<key>/<token>/<format>?...) return the image for <img> use.

If you build a provider-agnostic wrapper, normalize to "bytes plus a stored URL" early. That is the shape both can produce with one extra step.

Test both on your own URLs

We have not run ScreenshotOne and Urlbox on our shared test corpus yet, so this page has no latency or success-rate table. The script below does the run for you. It sends the same 1280 x 800 full-page PNG request with ads and cookie banners handled to both services, saves every image, and records latency, bytes and pixel dimensions.

Method we recommend:

  1. Put 10 to 20 URLs in urls.txt: a static page, a long page with lazy images, a cookie-banner page, a single-page app, a page behind a CDN bot check, and the hardest pages from your own product.
  2. Run 3 times or more per URL. Keep caching off on both sides (cache=false for ScreenshotOne, force: true for Urlbox) so you measure renders.
  3. Report p50 and p95 latency per provider, and the share of runs that returned an image.
  4. Open the images side by side. Dimensions that differ for the same URL usually mean one provider stopped scrolling early or a banner changed the layout.
  5. Repeat on a different day. A single run is a sample, not a benchmark.

Node.js (Node 22+, no dependencies)

// Run the same capture through ScreenshotOne and Urlbox and record latency, bytes and dimensions.
// Usage: SCREENSHOTONE_ACCESS_KEY=... URLBOX_SECRET=... node compare.mjs urls.txt
import { readFile, writeFile, mkdir } from 'node:fs/promises';

const OPTS = { width: 1280, height: 800, fullPage: true, format: 'png' };
const RUNS = Number(process.env.RUNS ?? 3);

const providers = {
  // GET https://api.screenshotone.com/take returns the image bytes.
  async screenshotone(url) {
    const q = new URLSearchParams({
      access_key: process.env.SCREENSHOTONE_ACCESS_KEY,
      url,
      viewport_width: String(OPTS.width),
      viewport_height: String(OPTS.height),
      full_page: String(OPTS.fullPage),
      format: OPTS.format,
      block_cookie_banners: 'true',
      block_ads: 'true',
      cache: 'false',
    });
    const res = await fetch(`https://api.screenshotone.com/take?${q}`, { signal: AbortSignal.timeout(90_000) });
    if (!res.ok) throw new Error(`screenshotone ${res.status}: ${(await res.text()).slice(0, 200)}`);
    return Buffer.from(await res.arrayBuffer());
  },
  // POST /v1/render/sync returns JSON with a renderUrl; download it to compare bytes.
  async urlbox(url) {
    const res = await fetch('https://api.urlbox.com/v1/render/sync', {
      method: 'POST',
      headers: { Authorization: `Bearer ${process.env.URLBOX_SECRET}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        url,
        width: OPTS.width,
        height: OPTS.height,
        full_page: OPTS.fullPage,
        format: OPTS.format,
        hide_cookie_banners: true,
        block_ads: true,
        force: true,
      }),
      signal: AbortSignal.timeout(90_000),
    });
    if (!res.ok) throw new Error(`urlbox ${res.status}: ${(await res.text()).slice(0, 200)}`);
    const { renderUrl } = await res.json();
    const img = await fetch(renderUrl, { signal: AbortSignal.timeout(30_000) });
    return Buffer.from(await img.arrayBuffer());
  },
};

// Width and height from the PNG header (bytes 16-23).
const pngSize = (b) => (b.toString('ascii', 1, 4) === 'PNG' && b.length >= 24
  ? { w: b.readUInt32BE(16), h: b.readUInt32BE(20) } : null);

const urls = (await readFile(process.argv[2] ?? 'urls.txt', 'utf8')).split('\n').map((s) => s.trim()).filter(Boolean);
await mkdir('out', { recursive: true });
const rows = [];
for (const url of urls) {
  for (const [name, call] of Object.entries(providers)) {
    for (let run = 1; run <= RUNS; run++) {
      const t0 = performance.now();
      try {
        const img = await call(url);
        const ms = Math.round(performance.now() - t0);
        const slug = new URL(url).hostname.replace(/\W+/g, '-');
        await writeFile(`out/${slug}-${name}-${run}.png`, img);
        rows.push({ url, provider: name, run, ms, bytes: img.length, ...pngSize(img), ok: true });
      } catch (err) {
        rows.push({ url, provider: name, run, ms: Math.round(performance.now() - t0), ok: false, error: err.message });
      }
    }
  }
}
await writeFile('results.json', JSON.stringify(rows, null, 2));
console.table(rows.map(({ error, ...r }) => r));

Urlbox's latency in this script includes the download from renderUrl, because that is what your code waits for. If you only need the stored URL, time the render call alone.

Python

"""Same capture through ScreenshotOne and Urlbox; prints one JSON line per call."""
import json
import os
import sys
import time

import requests

W, H = 1280, 800


def screenshotone(url):
    r = requests.get("https://api.screenshotone.com/take", params={
        "access_key": os.environ["SCREENSHOTONE_ACCESS_KEY"], "url": url, "viewport_width": W,
        "viewport_height": H, "full_page": "true", "format": "png", "block_cookie_banners": "true",
        "block_ads": "true", "cache": "false"}, timeout=90)
    r.raise_for_status()
    return r.content


def urlbox(url):
    r = requests.post("https://api.urlbox.com/v1/render/sync",
                      headers={"Authorization": f"Bearer {os.environ['URLBOX_SECRET']}"},
                      json={"url": url, "width": W, "height": H, "full_page": True, "format": "png",
                            "hide_cookie_banners": True, "block_ads": True, "force": True}, timeout=90)
    r.raise_for_status()
    img = requests.get(r.json()["renderUrl"], timeout=30)
    img.raise_for_status()
    return img.content


for url in [u.strip() for u in open(sys.argv[1]) if u.strip()]:
    for name, call in (("screenshotone", screenshotone), ("urlbox", urlbox)):
        t0 = time.perf_counter()
        try:
            img = call(url)
            size = (int.from_bytes(img[16:20], "big"), int.from_bytes(img[20:24], "big")) if img[1:4] == b"PNG" else None
            print(json.dumps({"url": url, "provider": name, "ms": round((time.perf_counter() - t0) * 1000),
                              "bytes": len(img), "size": size, "ok": True}))
        except requests.RequestException as e:
            print(json.dumps({"url": url, "provider": name, "ok": False, "error": str(e)[:200]}))

Tested on 2026-10-15 with Node 22.18.0 and Python 3.14 (requests 2.34.2) against a local mock that imitates both APIs' request and response shapes. The mock confirmed the query string, the JSON body, the renderUrl download and the PNG header parsing. The parameter names come from each vendor's docs; we have not called either live API with these scripts, so check the first results by eye.

Where each one is stronger, from their own pages

ScreenshotOne publishes its plan table with features per tier, bills only successful and non-cached screenshots according to its pricing FAQ, and keeps a free tier of 100 screenshots a month. Its blocking options (block_cookie_banners, block_chats, block_trackers, block_banners_by_heuristics) are many and specific.

Urlbox lists a wide set of format values in one parameter (png, jpeg, webp, avif, svg, pdf, html, mp4, webm, md), separates sync, async and render-link APIs clearly, lists long request timeouts and no file-size cap on upper plans, and states uptime SLAs on Business and Enterprise. Its Lo-Fi plan is a cheap option for the narrow case of rendering your own pages.

Where RenderScreenshot fits

If neither one fits your price point or your stack, we are the third option to test with the same script: add a third provider that posts the same capture to https://api.renderscreenshot.com/v1/screenshot with viewport, capture.mode: "full_page" and block.cookie_banners. The ScreenshotOne alternative and Urlbox alternative pages map each one to our API in detail, and you can run the comparison with a free key.

Frequently asked questions

Which is cheaper, ScreenshotOne or Urlbox?
On list prices checked in October 2026, ScreenshotOne's entry plan is lower ($17 for 2,000 screenshots against Urlbox Hi-Fi at $49 for up to 5,000). Urlbox's Lo-Fi plan is $19 for 2,000 renders but is meant for sites you own and low-quality thumbnails of third-party sites. At 100,000 a month, the totals depend on which Urlbox plan and slider tier you need.
Do ScreenshotOne and Urlbox use the same option names?
Many are close but not identical. ScreenshotOne uses viewport_width and viewport_height; Urlbox uses width and height. ScreenshotOne has block_cookie_banners; Urlbox has hide_cookie_banners and click_accept. The mapping table on this page lists the common ones.
Does either one return the image directly?
ScreenshotOne's take endpoint returns the image bytes for a GET request. Urlbox's sync render endpoint returns JSON with a renderUrl you then download, and Urlbox render links return the image for use in img tags.
Why does this page not show latency numbers?
We have not run both services on our shared corpus yet. The script on this page runs the same capture through both and records latency, bytes and dimensions, so you can measure on your own URLs.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.