Node.js

Capture a website screenshot with Node.js.

Twenty lines, no SDK, and a file on disk at the end of them. The same request works from any runtime that can make an HTTP call — this one is written for Node because that is what most people reach for first.

What you need

  • Node.js 18 or newer — fetch and Buffer are built in, so there is nothing to install.
  • An API key from the dashboard, kept in an environment variable rather than in the file.
  • A free account, if you do not have one: 100 screenshots a month, no card.

Create the key on the API keys page. The plaintext key is shown once, at creation, and cannot be retrieved afterwards — store it before closing the dialog. Send it from your own server, never from browser or mobile code: a key in a client is readable by everyone who has the app.

The request

Save this as screenshot.mjs. It asks for one page at a 1280 × 800 viewport, at twice the pixel density, encoded as WebP — and writes the bytes that come back to a file.

screenshot.mjs
import { writeFile } from 'node:fs/promises';

const params = new URLSearchParams({
  url: 'https://en.wikipedia.org/wiki/Cartography',
  format: 'webp',
  quality: '80',
  viewport_width: '1280',
  viewport_height: '800',
  device_scale_factor: '2',
});

const response = await fetch(`https://api.urlshot.io/v1/screenshot?${params}`, {
  headers: { Authorization: `Bearer ${process.env.URLSHOT_API_KEY}` },
});

// Anything that is not an image is this envelope, whatever went wrong.
if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.code}: ${error.message} (request ${error.requestId})`);
}

await writeFile('screenshot.webp', Buffer.from(await response.arrayBuffer()));
console.log('Wrote screenshot.webp');

Run it

Bash
export URLSHOT_API_KEY=sk_live_your_key_here
node screenshot.mjs

One render, one credit. A repeat of the same request within the cache window you ask for costs nothing.

What comes back

On success, the image bytes themselves — no JSON wrapper, no base64, nothing to decode. This is the file that program writes, shown at the width this page has for it:

The response carries what you need to account for the call, on every request:

Response headers returned with a screenshot.
X-Request-IDOpaque identifier for this request. Quote it in any support conversation.
X-Urlshot-CacheHIT when the image was served from cache, MISS when it was rendered, BYPASS when cache_ttl was 0.
X-Urlshot-Credits-UsedRender credits consumed by this request. A cache hit consumes zero.
X-RateLimit-LimitRenders your plan may run at once.
X-Urlshot-RendererThe renderer build that produced the image, such as v4:4572cafff9ea; on a cache hit, the build that made the cached image. For support conversations, not for branching on.
X-RateLimit-RemainingRender slots still free after this request. Absent on a cache hit, where it is unknown rather than zero.
Cache-ControlHow long a browser may reuse the image: private, max-age=<seconds> when cache_ttl is set, and on a cache hit no longer than the cached copy has left; no-store when cache_ttl is 0.

A failed call returns JSON instead, with a stable code and the request id to quote if you ask about it — which is what the !response.ok branch above reads. The codes, and which of them are worth retrying, are listed in the error reference.

Options worth knowing

Every option is a query parameter, and every one has a default — add them to the URLSearchParams above as strings. These are the six that change a capture the most:

Six of the 16 options, with their ranges and defaults.
full_pageCapture the full scrollable page instead of only the viewport.boolean
formatOutput image format.png | jpeg | webp
qualityEncoder quality for jpeg and webp. Defaults to 80 for those formats and must be omitted for png.1–100
viewport_widthViewport width in CSS pixels.200–3840
dark_modeEmulate prefers-color-scheme: dark before navigation.boolean
hide_selectorsCSS selector whose matching elements are hidden after navigation. Repeatable.string[]

full_page=true is the one people come for: the browser scrolls to the bottom and the capture is the whole document rather than the viewport. All 16 are in the parameter reference.

Where to go next

  • The API reference — every parameter, response header and error code, generated from the contract the gateway validates against.
  • Signed URLs — sign a query on your server and the resulting address is safe to publish, so a capture can go straight into a page.
  • Pricing — what a credit costs once the free allowance runs out.

Run it with your own key.

100 screenshots a month on the free plan. No card needed.