The screenshot API, explained
How server-side page capture actually works, why the results are so often unusable, and what to do about it.
What a screenshot API is
A screenshot API is an HTTP endpoint that runs a real browser on a server, points it at a URL you give it, and hands back the rendered page as an image. You send a URL, you get a PNG.
The reason to buy one rather than build one is not the screenshot. It is everything around it: keeping a headless Chrome binary alive, giving it enough memory to render a heavy page, stopping one slow target from blocking every other request, and doing that on hardware you are not paying for while it sits idle. The capture is a few lines. The operations are the product. Puppeteer or an API compares the two in detail.
Why your screenshots have a cookie banner over them
This is the failure that sends most people looking for a second provider, so it is worth being precise about the cause.
A headless browser is a first-time visitor. It has no cookies, no local storage and no consent record. Every consent platform is built to interrupt exactly that visitor, so a naive capture of a European news site returns a dark overlay and an Accept button, with the content you wanted greyed out behind it. Sticky headers eat the top of the frame and a live-chat bubble sits in the bottom-right corner of every single image.
Cropping does not fix it: the overlay is in the middle of the page and the content behind it is dimmed and often scroll-locked. Clicking Accept is worse — you are recording consent on behalf of someone who never gave it, once per capture.
The workable answer is to remove the overlay from the DOM before the capture, and to unstick whatever is fixed to the viewport. That is what Framejet calls clean mode, and it runs by default rather than behind a flag you have to remember. You turn it off with clean=false when you want the page exactly as a visitor would meet it.
Making the call
The response body is the image itself, not a JSON envelope with a link you have to fetch afterwards. That means you can pipe it straight to a file from an authenticated request.
Full reference, including every parameter and error code, is on the docs page.
The parameters that actually matter
| Parameter | Why you would reach for it |
|---|---|
full_page | Capture the whole scroll height instead of one viewport. Height is then decided by the content, so height is ignored. |
width | The viewport width the page believes it is rendering into, 320–3840. This is how you get a mobile-layout capture: set a narrow width, not a small image. |
dpr | Device pixel ratio, 1–3. Set 2 for a retina-resolution image at the same layout width. |
delay | Extra wait after load, up to 10 seconds. The escape hatch for pages that animate in or fetch their content after first paint. |
cache | Set false when you are monitoring a page for change and a cached answer would defeat the point. |
Retina screenshot API: sharper pixels, same layout
Viewport size and image resolution are different controls. Set width=1280 and height=800 to render the page at a 1280-by-800 desktop layout. Add dpr=2 and the returned image has 2560-by-1600 pixels, while the page still lays itself out at 1280 CSS pixels wide. Doubling width instead can change the responsive layout.
Framejet accepts dpr from 1 to 3. Higher pixel density makes text and thin lines clearer when you display the image on a high-density screen, but it also multiplies the pixel count and file size. A capture above 32 million pixels or 4 MB is rejected rather than returned as a partial image. For a full-page screenshot, start at dpr=1 and increase it only if the page fits. The full page screenshot guide lists every limit.
If the page you need appears only after a click or search, follow the capture-after-clicking guide for selector actions and goal-based navigation.
Screenshots for AI agents and vision models
This is newer than the rest of the category and worth separating out. When a vision model reads a page capture, an overlay is not a cosmetic problem — it is the model's answer. A consent wall covering the article means the model describes the consent wall.
Framejet exposes the same capture engine as a remote Model Context Protocol server at https://framejet.dev/mcp. An agent authenticates with the same API key as a bearer token, calls one screenshot tool, and gets the image inline. It draws from the same monthly quota as your REST calls, so there is no second account to manage. Setup is on the docs page.
What it costs to run, and how that reaches your bill
A capture is a browser launch, a page load and an image encode. The page load is the part that varies wildly — a static page is a few hundred milliseconds, a heavy application can be several seconds. Two billing details follow from that, and they are worth checking on any provider you evaluate:
- Repeats. The same URL with the same parameters is the same image. Framejet serves it from cache for seven days and does not meter it.
- Failures. A timeout produced no image, so Framejet spends no credit on it. You pay for pixels you actually received.
Neither of these is unique to Framejet, and you should confirm both with whoever you buy from — the sourced comparisons are at vs ScreenshotOne, vs Urlbox and vs ApiFlash.
When a capture fails
Four things account for nearly every failure, and knowing which one you hit saves an afternoon:
| What you see | What happened |
|---|---|
target_timeout | The page never finished loading. Often a third-party script that never resolves — try a shorter target or a delay. |
target_error | The site refused the request with an HTTP error, usually bot protection. chatgpt.com, for example, answers 403. Framejet doesn't try to get past it. |
blocked_target | The URL resolves to a private or internal address. Refused on purpose: a capture service that fetches internal addresses is a way into your network. |
bad_url | Not an http or https URL, or not a URL at all. |
| A blank or half-rendered image | The page painted after the capture. This is what delay exists for. |