DEV Community

Cover image for How to Capture a Webpage Screenshot Programmatically (URL to Image via API)
Nico Acosta for Grabbit

Posted on • Originally published at grabbit.live

How to Capture a Webpage Screenshot Programmatically (URL to Image via API)

To capture a webpage screenshot from your code, send the page URL to a screenshot API and it returns a hosted image. One HTTP request in, one image URL out. The headless browser that renders the page runs in the cloud, so there is no Chromium to install and no browser process to keep alive.

That is the fast path. Below are the ways to capture a webpage, when each one fits, and how to get the full-page, format, and timing options right.

The quick answer

If you just want a URL captured as an image from your code, this is the whole thing:

curl https://api.grabbit.live/v1/grabs \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "width": 1280,
    "height": 720,
    "format": "webp"
  }'
Enter fullscreen mode Exit fullscreen mode

The response is JSON with a hosted image_url:

{
  "id": "grb_01jx...",
  "status": "done",
  "target_url": "https://example.com",
  "image_url": "https://cdn.grabbit.live/grabs/grb_01jx....webp",
  "width": 1280,
  "height": 720,
  "format": "webp",
  "bytes": 62140,
  "execution_ms": 940,
  "created_at": "2026-07-27T09:00:00.000Z"
}
Enter fullscreen mode Exit fullscreen mode

Store that image_url, embed it, or hand it to another service. The rest of this guide is about picking the right method and getting the capture right.

Manual versus programmatic capture

"Capture a webpage screenshot" splits into two different jobs, and the right tool depends on which one you have.

Manual, one page. You are looking at a page and want to save it. Your browser already does this. In Chrome or Edge, open the command menu with Ctrl+Shift+P (Cmd+Shift+P on Mac), type "screenshot," and choose "Capture full size screenshot" for the whole page. Edge has a dedicated shortcut: Ctrl+Shift+S, then "Capture full page." An extension like GoFullPage does the same through a toolbar button. For one page you are looking at right now, use one of these and move on.

Programmatic, repeatable. You need to capture pages from code, on a schedule, or across many URLs. The manual path falls apart here: you cannot click a browser menu inside a cron job, a CI pipeline, or a webhook handler. You need an endpoint you can call. That is the rest of this guide.

Capturing a webpage with an API

For anything repeatable, a screenshot API is the shortest path. You already saw the curl call. Here is the same capture in a few languages, since "how do I do this in my stack" is the real question.

Python, using requests:

import requests

resp = requests.post(
    "https://api.grabbit.live/v1/grabs",
    headers={"Authorization": "Bearer sk_live_..."},
    json={
        "url": "https://example.com",
        "width": 1280,
        "height": 720,
        "format": "webp",
    },
)
data = resp.json()
print(data["image_url"])
Enter fullscreen mode Exit fullscreen mode

Node.js, using the built-in fetch:

const resp = await fetch('https://api.grabbit.live/v1/grabs', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer sk_live_...',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://example.com',
    width: 1280,
    height: 720,
    format: 'webp',
  }),
});
const data = await resp.json();
console.log(data.image_url);
Enter fullscreen mode Exit fullscreen mode

Every one of these is a plain HTTP call. No chromedriver, no 300 MB Chromium download, no async browser session to tear down. That is the whole reason to reach for an API over a local capture: the rendering machinery lives somewhere else.

Capturing the full page, not just the viewport

By default a capture stops at the viewport height you request. To grab the entire scrollable page as one tall image, set full_page to true:

curl https://api.grabbit.live/v1/grabs \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "width": 1280,
    "full_page": true,
    "format": "webp"
  }'
Enter fullscreen mode Exit fullscreen mode

With full_page on, height is ignored and the render extends to the bottom of the document. This is the "scrolling screenshot" people ask for: everything below the fold, in one image. width must be between 320 and 1920 pixels; height, when you use it, between 240 and 1080.

Handling pages that load content late

The reason a real browser beats a raw-HTML fetch is that it runs JavaScript. Single-page apps, lazy-loaded images, and content that appears a beat after load all render the way they do for a visitor, because the capture happens in headless Chromium, not an HTML parser.

Two options control the timing:

  • delay_ms waits a fixed number of milliseconds (0 to 10000) after load before firing the capture. Use it when content animates or streams in.
  • selector waits for a specific element to appear and captures once it does. Use it when you know the exact node that signals "the page is ready."
curl https://api.grabbit.live/v1/grabs \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/dashboard",
    "width": 1440,
    "height": 900,
    "delay_ms": 1500,
    "format": "png"
  }'
Enter fullscreen mode Exit fullscreen mode

This is also why an API can capture pages where a browser extension fails. A page can break extension-based capture with its layout, but a server that loads the URL like a normal visitor still gets a clean render of what the browser paints.

Choosing PNG, JPEG, or WebP

The format field takes png, jpeg, or webp, and picking wrong means either bloated files or lost quality.

  • WebP is the best default. For a typical webpage render it produces the smallest file at the same visual quality, which adds up fast when you store and serve thousands of captures. Use it unless you have a reason not to.
  • PNG is lossless and supports transparency. Reach for it when exact pixel fidelity matters, for example a baseline image in a visual regression test.
  • JPEG is the safest bet for older tooling that predates WebP. It is lossy, so text edges soften slightly.

Switching format is a one-word change in the request body, so you can experiment without rewriting anything.

When a local capture is the better call

An API is not always the answer. Capture locally instead when:

  • You are doing a single manual capture. Use the browser shortcut from earlier.
  • You already have the HTML, not a URL. If the markup lives in your app and is never served at an address, a local render avoids a round trip. See HTML to image for that pattern, including hosting your template at a URL and capturing it.
  • You cannot make outbound requests. In an air-gapped environment an API is off the table, so a bundled headless browser is the only option.

For everything else, especially anything on a schedule or in CI, the API path keeps your deployment small and removes a whole class of "it works locally but not in the container" failures.

Pricing is worth a word here because it is where these tools differ most. Grabbit charges a flat $0.002 per live capture as prepaid credits that never reset or expire monthly, so a batch that runs once a quarter does not forfeit an unused monthly allowance. That is different from the metered monthly quotas most screenshot tools bill on.

Next steps

The screenshot API covers the full parameter set, authentication, and rate limits. If you are wiring this into a job, the screenshot from a URL guide walks the request end to end, screenshot automation covers running captures on a schedule without a browser, and screenshot a list of URLs handles capturing many pages at once.


Originally published on the Grabbit blog.

Top comments (0)