Skip to content
CalculatorBy 11 min read

Screenshot API or your own headless browser: the real cost

The code is 8 lines. The cost is everything around it. Put your own numbers into the calculator, see the work that hides under the waterline, and decide with a checklist that says when running it yourself is the better call.

a month = servers × server price + maintenance hours × hourly cost

first year = build hours × hourly cost + 12 × a month

What does running your own headless browser cost per month?

A self-hosted screenshot service costs servers plus people, and the people are usually the larger part. Every input below is yours to change. Only the server price is a published figure[7]; the rest are round placeholders, not industry averages.

Your numbers

Example onlySuccessful renders you expect in a month.

Example onlyOpen the page, wait until it is ready, capture, encode. Measure it on your own pages.

Example onlyTraffic is never flat. 4 means the peak is four times the monthly average.

Published price[7]One machine that runs browser workers.

Example onlyHow many pages it can render at the same moment without slowing down. Measure it.

Example onlyOne-off: workers, queue, storage, safety checks, deployment.

Example onlyBrowser updates, broken pages, alerts, capacity.

Example onlySalary plus overhead, or the rate you pay.

Running it yourself

$224a month, after it is built

Infrastructure
$24 (1 server)
People
$200 (4 h)
Build cost, once
$2,000
First year, self-hosted
$4,688
First year, API
$948

The API side

Growth covers 10,000 screenshots a month: $79 a month, up to 15,000 screenshots, 150 requests a minute, 15 at once. All plans

Break-even

With these numbers self-hosting never catches up: it costs $145 more every month, on top of $2,000 to build.

  • Self-hosted, 12 months: $4,688
  • API, 12 months: $948
StartMonth 6Month 12

The formulae, in plain text

Renders one server finishes in a month
= renders at once × 2,592,000 seconds ÷ seconds per render
Servers needed
= screenshots a month × peak factor ÷ renders one server finishes in a month, rounded up, at least 1
Infrastructure a month
= servers needed × server price a month
People a month
= maintenance hours a month × cost of one hour
Self-hosting a month
= infrastructure a month + people a month
Build cost, once
= hours to build × cost of one hour
First year, self-hosted
= build cost + 12 × self-hosting a month
First year, API
= 12 × price of the plan that covers your volume
Break-even, in months
= build cost ÷ (API price a month − self-hosting a month), only when the API costs more a month

A 30-day month has 30 × 24 × 3,600 = 2,592,000 seconds.

Worked example with the defaults

  1. Renders one server finishes in a month: 2 × 2,592,000 ÷ 5 = 1,036,800.
  2. Servers needed: 10,000 × 4 ÷ 1,036,800 = 0.039, rounded up to 1.
  3. Infrastructure a month: 1 × $24 = $24.
  4. People a month: 4 hours × $50 = $200.
  5. Self-hosting a month: $24 + $200 = $224.
  6. Build cost, once: 40 hours × $50 = $2,000.
  7. First year, self-hosted: $2,000 + 12 × $224 = $4,688.

What the example shows

Of $224 a month, $200 is people and $24 is the server. One machine could finish 1,036,800 renders a month if it never idled, so at 10,000 a month the hardware is nearly idle. Change the hours and the hourly cost first: they move the result most.

Where each default comes from

Show the table
InputDefaultWhere the default comes from
Screenshots a month10,000Example only Your number here, example only
Average seconds per render5Example only Your number here, example only
Peak compared with average4Example only Your number here, example only
Server price a month$24Published price DigitalOcean Basic Droplet, Regular CPU, 2 vCPUs and 4 GiB of memory: $24.00 a month on the public price list, read on 11 October 2026[7]
Renders one server runs at once2Example only Your number here, example only
Engineering hours to build it40Example only Your number here, example only
Hours a month to maintain it4Example only Your number here, example only
Cost of one engineering hour$50Example only Your number here, example only

What is below the waterline when you run headless Chrome yourself?

Launching a browser and calling screenshot() is the visible tip. A screenshot service that other people depend on also does these 11 jobs, and each one is discovered the first time it goes wrong.

Above the waterline: what the tutorial shows

Launch a browser, call screenshot()

8 lines of code

  1. Crashes, memory and recyclingA browser is a heavy, long-running process. Workers need memory limits, restarts and cleanup of processes left behind. Playwright warns that Chromium in Docker can run out of memory and crash without the right flags.[3]
  2. Concurrency and queuesOne server renders only a few pages at the same moment. Requests beyond that have to wait in a queue, with a limit, an order and a way to report progress.
  3. Fonts, emoji and CJK textA bare Linux server has few fonts. Puppeteer’s own Docker recipe installs extra font packages for Chinese, Japanese, Arabic, Hebrew and Thai; without them those scripts render as empty boxes.[4]
  4. Lazy loading and waitingThe load event fires before lazy images and client-rendered content arrive. You decide what "ready" means, and scroll the page so a full-page capture has no blank bands.
  5. Cookie banners and adsConsent dialogs, ads and chat widgets sit on top of the content until you block or hide them, and the rules that do it need upkeep.
  6. Timeouts and retriesSome pages never finish loading. Every step needs a deadline, and a failed render needs a retry policy that does not hammer a broken page.
  7. SSRF protectionWhen the address comes from a user, the browser can be pointed at your own network. Addresses, redirects and DNS answers all have to be checked.[6]
  8. Sandboxing untrusted pagesAny page can run code in your browser. Chromium’s sandbox has to stay on, which rules out the common shortcut of running as root with --no-sandbox.[4]
  9. Keeping Chromium updatedBrowser security fixes ship regularly. Each update can change rendering, so it needs a test pass before it reaches production.
  10. Caching and storageScreenshots are files: they need a place to live, an expiry, and a cache so the same page is not rendered a thousand times.
  11. MonitoringQueue depth, render time, failure rate and memory per worker, with an alert that reaches a person before the customers notice.

Below the waterline: what a service other people depend on also does

The 8-line script above the waterline, and the 11 layers of work under it, roughly in the order teams meet them.

The minimal Playwright script, and what it leaves out

These 8 lines of Node.js take a full-page screenshot with Playwright, and they do work. The script follows the first script in Playwright's library guide[1], with Chromium as the browser and the fullPage option from its screenshots guide[2]. It ran as written against Playwright 1.63 on 11 October 2026.

screenshot.js
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: 'example.png', fullPage: true });
  await browser.close();
})();

Install first: npm i -D playwright, then npx playwright install chromium[1]. Puppeteer's version has the same shape: launch, new page, goto, screenshot, close.

What those lines do not handle

  • A page that never finishes loading

    It relies on the library’s default timeouts. When one fires the script throws: nothing retries, and the browser is left running.

  • Two requests at once

    It launches a whole browser per run. Ten callers means ten browsers, with nothing to limit or queue them.

  • Lazy-loaded images

    fullPage captures the full scrollable page, but nothing scrolls it first, so content that loads on scroll can be missing.[2]

  • Cookie banners, ads and chat widgets

    Whatever the page shows a first-time visitor is in the image.

  • Missing fonts on the server

    It renders with the fonts the machine has. A minimal Linux image lacks many scripts and emoji.[4]

  • An address chosen by someone else

    page.goto opens whatever it is given, including addresses inside your network.[6]

  • A crash

    If the browser dies, the promise rejects and browser.close() never runs. Nothing restarts it or cleans up.

  • Where the file goes

    It writes example.png to the local disk. No storage, no expiry, no cache.

Headless browser vs screenshot API, side by side

Your own headless browser gives you complete control at a mostly fixed cost; a screenshot API gives you less control at a cost that follows use. Neither column wins every row.

Your own headless browserA screenshot API
ControlComplete. Any browser flag, any script, any sequence of clicks and typing.The options the API offers. Usually wide for capture, narrow for multi-step interaction.
Time to first screenshotMinutes on your laptop (8 lines). A production service takes the build hours you enter in the calculator.One HTTP request after you have a key.
Ongoing workYours: browser updates, fonts, blocked pages, capacity, alerts.The provider’s. You keep an eye on usage and handle error responses.
ScalingYou add servers and tune the queue. You pay for peak capacity even when idle.Bounded by the plan: a monthly quota, a rate limit and a concurrency limit.
Pages behind a login or firewallAnything your network reaches, including intranet hosts and localhost.Public pages, and signed-in pages when you send a cookie or header. Private addresses are out of reach.
Security surfaceYou run a browser that opens arbitrary pages inside your infrastructure: SSRF checks, sandbox and patching are yours.The browser runs outside your network. You send page addresses, and any cookies you pass, to a third party.
Cost shapeMostly fixed: servers plus people, almost flat until you need another server.Per use: a plan sized by screenshots a month, stepping up with volume.
Failure handlingYou write it: timeouts, retries, crash recovery, dead jobs.An error code per failure. You still decide whether to retry.

Two architectures: five moving parts or one HTTP call

A self-hosted screenshot service is five components you run: a queue, an address check, browser workers, storage and monitoring. With an API the same components exist, on the provider's side of one HTTPS request.

Your own headless browser

Your appQueuejobs waitingAddress checkSSRF guardBrowser workersChromiumChromiumChromiumStoragefiles and cacheMonitoringand alerts
Self-hosted. Your app puts a job on a queue, an address check approves it, browser workers render it, the file goes to storage. Monitoring watches the queue, the workers and the storage.

A screenshot API

Your appOne HTTPSrequestThe imageScreenshot APIrun by the provider, not by youQueueAddress checkBrowser workersStorageMonitoring
API. Your app sends one HTTPS request with the page address and gets the image back. The other parts are run by the provider.

Is it safe to screenshot URLs that users submit?

A screenshot service that accepts addresses from users is a server that opens URLs someone else chose. Two protections make that safe, and both are your job when the browser is yours.

Server-side request forgery

In a server-side request forgery (SSRF) attack, the attacker abuses a feature of the server to read or change internal resources by choosing the URL the server fetches[6]. A headless browser inside your network is exactly such a feature. OWASP lists what it can be made to read:

  • Cloud metadata, served at http://169.254.169.254/, which can hold configuration and sometimes credentials
  • HTTP interfaces of internal databases
  • Internal REST interfaces
  • Local files, through file:// addresses

The defence is to resolve and check every address before the browser opens it, to repeat the check on every redirect and every request the page makes, and to refuse anything that is not public.

The Chromium sandbox

Chromium's sandbox limits what a page can do to the machine if it gets code running inside the browser. Its design document applies least privilege and assumes that sandboxed code is malicious[5].

Running without a sandbox is strongly discouraged.
Puppeteer troubleshooting guide[4]

The same guide allows --no-sandbox only "if you absolutely trust the content you open". Playwright's Docker guide notes that the default root user disables the Chromium sandbox, and recommends a separate user and a seccomp profile for crawling untrusted sites[3]. A screenshot service opens pages it does not control, so the shortcut is not available to it.

What an API changes, and what it does not

An API moves the browser out of your network, so a hostile address cannot reach your internal services through it. The risk does not vanish: the provider has to do the same work, and you send it the page addresses and any cookies you pass along.

When should you run it yourself, and when should you use an API?

Run your own headless browser when control, private pages or steady high volume matter most; use an API when time and upkeep do. Count the lines that are true for you.

Run it yourself if

  • You already run browser automation in production, with the people and the monitoring for it.
  • The pages are behind your firewall: intranet tools, localhost, staging hosts with no public address.
  • You need full scripted interaction: multi-step flows, typing, file uploads, custom browser flags.
  • Your volume is high and steady, so servers stay busy and the fixed cost is spread over many renders.
  • Page content or addresses may not leave your infrastructure or a given region.
  • Screenshots are a by-product of tests you already run in a browser, such as visual regression checks.

Use an API if

  • Screenshots are a feature of your product, not the product, and nobody wants to own a browser fleet.
  • Volume is low, spiky or unknown, so a server would sit idle most of the month.
  • The addresses come from your users, and you would rather not run the browser that opens them inside your network.
  • You need it this week: one HTTP request instead of a queue, workers and storage.
  • The pages are public, or reachable with a cookie, a header or basic authentication.
  • Maintenance hours are worth more to you spent on your own product.

Many teams end up with both: their own browser for tests and pages inside the network, an API for screenshots of public pages in the product.

What one screenshot API request looks like

With CurlShot, a full-page screenshot of a public page without its cookie banner is one request. These are the operating facts its documentation states, nothing more.

cURL
curl "https://curlshot.com/api/v1/screenshot?access_key=YOUR_ACCESS_KEY&url=https://example.com&full_page=true&block_cookie_banners=true" \
  --output example.png
  • One successful render uses one screenshot from the monthly quota, whatever its size. A request that fails with any error is not counted. Usage and limits
  • With cache=true a repeat of the same request is served from a stored copy, and a cache hit is not counted either. Caching
  • With async=true the API answers at once with a job; you poll the job or receive a webhook when it is done. Async and webhooks
  • One bulk call starts up to 100 screenshots, each as its own job. Bulk screenshots
  • Every plan has three limits: a monthly quota, requests per minute and renders at the same moment. The three limits
  • Signed-in pages work by sending cookies, headers or an authorization value. localhost and private network addresses are refused, so pages behind your firewall need your own browser. Screenshot behind a login

Glossary: headless browser, SSRF and the rest

Headless browser
A real web browser running without a visible window, driven by a program instead of a person. It loads and renders pages exactly as a normal browser does.
Puppeteer
A JavaScript library, maintained by the Chrome team, that controls Chrome or Firefox from Node.js.
Playwright
A browser automation library from Microsoft that controls Chromium, Firefox and WebKit, with bindings for JavaScript, Python, Java and .NET.
SSRF
Server-side request forgery: an attack that makes a server send requests to an address the attacker chooses, typically to reach internal services the attacker cannot reach directly.
Concurrency
How many renders run at the same moment. It is limited by the memory and CPU of the machine, and it sets how much traffic one server can take at a peak.
Cold start
The delay while a browser process starts before it can open its first page. Services avoid it by keeping browsers running, which costs memory while they wait.
Queue
A list of render jobs waiting for a free browser. It turns a burst of requests into a steady flow the workers can handle.

Questions people ask

Is it cheaper to run Puppeteer or Playwright myself than to use a screenshot API?

Only when your engineering time is already paid for or your volume is high and steady. The servers are the small part: in the worked example on this page the server is $24 a month, and 4 hours of maintenance at an example $50 an hour is $200. Put your own hours and rates in the calculator; the answer follows the people cost.

How many screenshots can one server take?

Renders at once × 2,592,000 seconds in a 30-day month ÷ seconds per render. A server that runs 2 renders at once at 5 seconds each can finish 1,036,800 a month if it is never idle. Real traffic has peaks, so divide by how much busier your busiest moment is than the average. Both inputs are yours to measure; neither is an industry figure.

When is running my own headless browser the better choice?

When you already operate browser automation, when the pages are behind your firewall or on localhost, when you need multi-step scripted interaction, when volume is high and steady enough to keep servers busy, or when page content may not leave your infrastructure.

Why is rendering user-supplied URLs a security risk?

Because the browser runs inside your network and opens whatever address it is given. That is server-side request forgery: OWASP describes attackers using it to read cloud metadata at http://169.254.169.254/, internal HTTP interfaces and local files through file:// addresses. Every address, redirect and DNS answer has to be checked before the browser opens it.

Can I run headless Chrome with --no-sandbox?

Only for content you fully trust. Puppeteer’s documentation says running without a sandbox is strongly discouraged, and Playwright’s Docker guide recommends a separate non-root user and a seccomp profile for crawling untrusted sites. A screenshot service opens pages it does not control, so the sandbox should stay on.

Does CurlShot count failed screenshots?

No. On CurlShot one successful render uses one screenshot from the monthly quota. A request that fails with any error is not counted, and neither is a cache hit when caching is turned on for the request.

Sources

  1. [1]Playwright, Library: first script. Checked .
  2. [2]Playwright, Screenshots. Checked .
  3. [3]Playwright, Docker. Checked .
  4. [4]Puppeteer, Troubleshooting: Chrome sandbox, Docker and fonts. Checked .
  5. [5]The Chromium Projects, Sandbox design document. Checked .
  6. [6]OWASP Foundation, Server Side Request Forgery. Checked .
  7. [7]DigitalOcean, Droplet pricing. Checked .

Screenshot any page in one step

Paste an address into CurlShot and download the screenshot. A free account gives you 100 screenshots every month. No card needed.