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.
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
export URLSHOT_API_KEY=sk_live_your_key_here
node screenshot.mjsOne 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:
| X-Request-ID | Opaque identifier for this request. Quote it in any support conversation. |
|---|---|
| X-Urlshot-Cache | HIT when the image was served from cache, MISS when it was rendered, BYPASS when cache_ttl was 0. |
| X-Urlshot-Credits-Used | Render credits consumed by this request. A cache hit consumes zero. |
| X-RateLimit-Limit | Renders your plan may run at once. |
| X-Urlshot-Renderer | The 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-Remaining | Render slots still free after this request. Absent on a cache hit, where it is unknown rather than zero. |
| Cache-Control | How 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:
| full_page | Capture the full scrollable page instead of only the viewport. | boolean |
|---|---|---|
| format | Output image format. | png | jpeg | webp |
| quality | Encoder quality for jpeg and webp. Defaults to 80 for those formats and must be omitted for png. | 1–100 |
| viewport_width | Viewport width in CSS pixels. | 200–3840 |
| dark_mode | Emulate prefers-color-scheme: dark before navigation. | boolean |
| hide_selectors | CSS 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.