Browserbase Alternative for Screenshot Jobs

A Browserbase alternative for capture-only work: map session code to one API call, compute cost per screenshot, and migrate in Node, Python or Ruby.

By Simon Chiu · Updated

If you use Browserbase to open a URL, wait and take a screenshot, you are paying for a session platform to do the job of a screenshot API. RenderScreenshot replaces that whole session (create, connect, navigate, wait, capture, close) with one HTTP request, and bills per capture instead of per browser-minute. If your sessions log in through forms, click through flows or run AI agents, Browserbase is the better tool, and this page says so.

Disclosure: I build RenderScreenshot. Browserbase details on this page describe its documented session model (create a session, connect over CDP, pay for browser time). Check Browserbase's documentation and pricing for current plans and limits before you decide.

This page sits in our screenshot API comparison series. For a wider view of hosted browsers, including Browserless, Steel and Cloudflare Browser Rendering, see the best headless browser APIs.

Verdict

Choose Browserbase if Choose RenderScreenshot if
Your job logs in through a UI, clicks, types or follows a multi-step flow Your job is "open URL, wait, capture, store"
You run AI agents that decide what to do next on the page You want images, PDFs or social cards with presets
You need session recordings and a live view to debug You want a typed error and a retry flag instead of a recording
You need to keep logged-in browser contexts between runs You can pass the session as cookies or headers
Your captures are a side effect of a larger automation Captures are the product (previews, archives, OG images)

Session platform vs capture API

Browserbase sells browser time. You create a session, connect Playwright, Puppeteer or Selenium to it over the Chrome DevTools Protocol, and pay while it is open. Everything that happens inside the session is your code: viewport, waits, cookie banners, lazy images, retries, where the file goes.

RenderScreenshot sells captures. You send the URL and options; the service runs the browser, applies the waits and blocking you asked for, and returns the image. The browser lifecycle, the retries inside the render and the cache are ours to run.

The practical difference for a screenshot job:

Step Browserbase (your code) RenderScreenshot (one request)
Start a browser bb.sessions.create() then chromium.connectOverCDP(session.connectUrl) Not needed
Set the viewport page.setViewportSize({ width: 1280, height: 800 }) viewport.width, viewport.height (or width, height in GET)
High-DPI output deviceScaleFactor on a new context viewport.scale (1 to 3)
Mobile device Playwright device descriptor viewport.device, for example iphone_14_pro
Wait for the page page.goto(url, { waitUntil: 'networkidle' }) wait.until: "networkidle"
Wait for an element page.locator(sel).waitFor() wait.for_selector
Extra settle time A fixed sleep after load wait.delay (ms)
Hide or remove elements page.addStyleTag(...) page.hide, page.remove
Cookie banners and ads Your own selectors or extensions block.cookie_banners, block.ads, block.chat_widgets
Logged-in pages Saved context or a scripted login network.cookies, network.headers, network.auth
Full page page.screenshot({ fullPage: true }) capture.mode: "full_page" with capture.scroll for lazy images
One element locator.screenshot() capture.mode: "element" with capture.selector
PDF page.pdf({ format: 'A4' }) output.format: "pdf" with the pdf options
Store the file Your code uploads it X-Cache-URL CDN link, or storage to your own bucket
Close the browser browser.close() (or you keep paying) Not needed

Every RenderScreenshot name in that table is documented in the parameter reference, wait options and capture options.

Cost per screenshot, worked out

Browserbase bills browser hours by plan (see their pricing page for the rates and included hours). RenderScreenshot bills one credit per standard capture. The two only become comparable once you know how many billed seconds one capture takes in your Browserbase account: session creation, connect, navigation, waits and capture, plus any minimum billing increment per session.

Measure that number first. The bench-cdp.mjs harness from our headless browser API comparison records it per run; for Browserbase, put the sessions.create() call inside its loop. Then compute the break-even browser-hour price:

// break-even.mjs: at what browser-hour price does a session platform cost the same as a per-capture API?
// Usage: node break-even.mjs <captures per month> <average billed seconds per capture>
const [captures, seconds] = process.argv.slice(2).map(Number);
if (!captures || !seconds) {
  console.error('usage: node break-even.mjs 10000 8');
  process.exit(2);
}

// RenderScreenshot self-serve plans, checked 2026-10-15. One credit per standard capture.
const plans = [
  { name: 'Starter', price: 10, credits: 2_500 },
  { name: 'Growth', price: 40, credits: 12_000 },
  { name: 'Scale', price: 100, credits: 60_000 },
];
const plan = plans.find((p) => p.credits >= captures);
if (!plan) {
  console.log('Above 60,000 captures a month: compare against a custom plan quote.');
  process.exit(0);
}

const perCapture = plan.price / captures;
const browserHours = (captures * seconds) / 3600;
const breakEven = plan.price / browserHours;

console.log(`RenderScreenshot ${plan.name}: $${plan.price}/month = $${perCapture.toFixed(4)} per capture`);
console.log(`Session platform: ${browserHours.toFixed(1)} browser-hours/month at ${seconds}s per capture`);
console.log(`Break-even: $${breakEven.toFixed(2)} per browser-hour, before base fees and included hours`);

Tested on 2026-10-15 with Node.js 22.18.0. The output for the volumes and session lengths most screenshot jobs fall into:

Captures per month RenderScreenshot plan Per capture Break-even at 5 s at 10 s at 20 s
1,000 Starter, $10 (2,500 credits) $0.0100 $7.20 / browser-hour $3.60 $1.80
10,000 Growth, $40 (12,000 credits) $0.0040 $2.88 / browser-hour $1.44 $0.72
60,000 Scale, $100 (60,000 credits) $0.0017 $1.20 / browser-hour $0.60 $0.30
100,000 Custom plan (above Scale) Quote Quote Quote Quote

How to read it: if your effective Browserbase cost per browser-hour, after base fees and included hours, is below the break-even number for your row, Browserbase is cheaper for that workload on raw compute. If it is above, the API is cheaper. Two effects move you across the line:

  • Long waits make sessions expensive. A single-page app that needs 15 seconds of network activity before it settles costs three times as much on a time-billed session as one that settles in 5 seconds. On a per-capture API, the wait does not change the price.
  • Cheap compute is not the whole cost. The table compares compute only. With a session platform you also maintain the wait logic, banner handling, retries and storage code. With the API, repeat captures of the same URL can come from cache without a new render.

Our own prices are on the pricing page. Every API response also carries X-Credits-Used, so you can total real costs from your logs.

What this page does not measure yet

We have not yet run Browserbase and RenderScreenshot side by side on the same URL list with an account on each, so this version has no head-to-head latency, success rate or rendered image comparison. When we publish that run, it will use the same harness and report p50 and p95 for cold and warm captures. Until then, run it yourself: the vendor test script covers the API side and bench-cdp.mjs covers the session side.

Migrate a Browserbase screenshot job

Before: a typical Browserbase capture

// Untested here: needs a Browserbase API key and project ID.
import Browserbase from '@browserbasehq/sdk';
import { chromium } from 'playwright';

const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create({ projectId: process.env.BROWSERBASE_PROJECT_ID });
const browser = await chromium.connectOverCDP(session.connectUrl);

try {
  const page = browser.contexts()[0].pages()[0];
  await page.setViewportSize({ width: 1280, height: 800 });
  await page.goto(process.argv[2] ?? 'https://example.com', { waitUntil: 'networkidle' });
  await page.locator('#onetrust-accept-btn-handler').click({ timeout: 2_000 }).catch(() => {});
  await page.screenshot({ path: 'capture.png' });
} finally {
  await browser.close();
}

After: Node.js with the SDK

// capture.mjs: the Browserbase screenshot job, as one RenderScreenshot call.
import { writeFile } from 'node:fs/promises';
import { Client, TakeOptions, RenderScreenshotError } from 'renderscreenshot';

const client = new Client(process.env.RS_API_KEY);
const target = process.argv[2] ?? 'https://example.com';

try {
  const png = await client.take(
    TakeOptions.url(target)
      .width(1280)
      .height(800)
      .waitFor('networkidle')
      .blockCookieBanners()
      .format('png'),
  );
  await writeFile('capture.png', png);
  console.log(`saved capture.png (${png.length} bytes)`);
} catch (err) {
  if (err instanceof RenderScreenshotError) {
    console.error(`${err.code} (retryable: ${err.retryable}): ${err.message}`);
  }
  process.exitCode = 1;
}

The hand-written banner click becomes blockCookieBanners(), which works across consent tools instead of one selector.

After: Python with plain HTTP

"""capture.py: the same capture with plain HTTP, no SDK needed."""
import os
import sys

import requests

API = "https://api.renderscreenshot.com/v1/screenshot"
target = sys.argv[1] if len(sys.argv) > 1 else "https://example.com"

resp = requests.post(
    API,
    headers={"Authorization": f"Bearer {os.environ['RS_API_KEY']}"},
    json={
        "url": target,
        "viewport": {"width": 1280, "height": 800},
        "wait": {"until": "networkidle"},
        "block": {"cookie_banners": True},
        "output": {"format": "png"},
    },
    timeout=60,
)
if resp.status_code != 200:
    error = resp.json().get("error", {})
    sys.exit(f"{error.get('code')} (retryable: {error.get('retryable')}): {error.get('message')}")

with open("capture.png", "wb") as f:
    f.write(resp.content)
print("saved capture.png, CDN copy:", resp.headers.get("X-Cache-URL"))

After: Ruby with the SDK

# capture.rb: the same capture with the Ruby SDK.
require 'renderscreenshot'

client = RenderScreenshot.client(ENV.fetch('RS_API_KEY'))
target = ARGV.fetch(0, 'https://example.com')

options = RenderScreenshot::TakeOptions
  .url(target)
  .width(1280)
  .height(800)
  .wait_for('networkidle')
  .block_cookie_banners
  .format('png')

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

The three "after" scripts were tested on 2026-10-15 against a local mock of the RenderScreenshot API (Node.js 22.18.0 with renderscreenshot 1.1.0, Python 3.14 with requests 2.34.2, Ruby 4.0.7 with the renderscreenshot gem 1.0.0), to check the request shape, file output and error path. The Python SDK is not on PyPI yet, which is why the Python version uses requests; see the Python SDK page for the GitHub install.

Response shape and auth differences

  • What comes back. With Browserbase you get a session; the image is whatever your code writes. With RenderScreenshot the response body is the image (or JSON with Accept: application/json), and headers carry X-Cache-URL (a public CDN link), X-Cache-Hit, X-Screenshot-Width, X-Screenshot-Height and X-Credits-Used. See response headers.
  • Keys. Server-side calls use an rs_live_ key as a Bearer token. To put a capture straight into an <img> or og:image tag, use a signed URL that expires (at most 30 days), so no key appears in your HTML.
  • Errors. Instead of reading a session recording, you get a typed error with a code and a retryable flag. Retry when retryable is true; fix the request when it is false.
  • Concurrency. You no longer manage a pool of sessions. For many URLs at once, the batch endpoint takes a list and runs it within your plan's limits.

Roll out without a big switch

Put both providers behind one function and pick per job type, not per environment:

// capture-router.mjs (fragment): route by job type, keep sessions where they still earn their cost.
export async function capture(job) {
  if (job.needsInteraction) return captureWithBrowserbase(job); // logins, clicks, agents
  return captureWithRenderScreenshot(job);                     // plain URL captures
}
  1. Mark each job type as plain capture or interactive.
  2. Move one plain job type, compare outputs for a week, then move the next.
  3. Keep Browserbase for the interactive types, so the session bill covers only the work that needs a session.

When to stay with Browserbase

  • Your captures are steps inside an AI agent's run, and the agent needs the session after the screenshot.
  • You log in through a form with MFA or a CAPTCHA flow that you cannot replace with a cookie.
  • You rely on session recordings and live view to debug customer issues.
  • Your measured billed seconds per capture put you well below the break-even price in the table above, and you are happy to own the capture code.

Try it on your own URLs

Create a free key (150 credits a month, no credit card), run capture.mjs against the same URLs your Browserbase job captures today, and compare the files and your billed seconds side by side.

Frequently asked questions

Is RenderScreenshot a drop-in replacement for Browserbase?
Only for jobs that open a page and capture it. RenderScreenshot does not give you a browser session to drive, so logins through a UI, multi-step flows and agent actions should stay on Browserbase.
Can RenderScreenshot capture pages behind a login?
Yes, if you can send the session as cookies, custom headers or HTTP basic or bearer auth with the request. If the login needs clicks and form input, keep that part in a browser session.
How do I compare cost per screenshot between Browserbase and an API?
Measure the billed seconds per capture on Browserbase, then compute the browser-hour price at which both cost the same. The break-even script on this page does the arithmetic for your volume.
Can I keep Browserbase and RenderScreenshot side by side?
Yes. Route plain captures to the API and keep sessions that need interaction on Browserbase. The adapter pattern on this page lets you switch per job type with one setting.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.