How to use ApiFlash with Node.js to screenshot a webpage
Capture a webpage with ApiFlash from Node.js, save the image safely, configure common options, and handle errors, limits, and credentials.
Use ApiFlash’s HTTPS endpoint, pass your access key and a complete webpage URL, then save the successful response stream as an image. The endpoint is https://api.apiflash.com/v1/urltoimage. Its default response is image bytes; check the HTTP status before writing the body to a file. Keep the access key on your server.
This guide uses Node.js built-in modules. The request pattern and options are based on ApiFlash’s official documentation; the example is an instructional adaptation, not independently tested code. See the ScreenshotNeo API documentation for the alternative one-call approach later in the guide.
1. Create an ApiFlash key and configure Node.js
Create an ApiFlash account and obtain an access key. Store it in an environment variable such as APIFLASH_ACCESS_KEY; do not put it in browser JavaScript, a committed source file, or a URL users can inspect.
Save the following as screenshot.js. It uses only Node.js built-in modules, so there are no packages to install.
const https = require('https');
const fs = require('fs');
const { pipeline } = require('stream');
const { promisify } = require('util');
const pipelineAsync = promisify(pipeline);
async function capture() {
const accessKey = process.env.APIFLASH_ACCESS_KEY;
if (!accessKey) {
throw new Error('Set APIFLASH_ACCESS_KEY before running this script');
}
const params = new URLSearchParams({
access_key: accessKey,
url: 'https://example.com',
format: 'png',
full_page: 'true'
});
const requestUrl = `https://api.apiflash.com/v1/urltoimage?${params}`;
const response = await new Promise((resolve, reject) => {
const request = https.get(requestUrl, resolve);
request.setTimeout(90000, () => {
request.destroy(new Error('ApiFlash request timed out'));
});
request.on('error', reject);
});
if (response.statusCode !== 200) {
let body = '';
response.setEncoding('utf8');
for await (const chunk of response) body += chunk;
throw new Error(`ApiFlash returned HTTP ${response.statusCode}: ${body}`);
}
try {
await pipelineAsync(response, fs.createWriteStream('screenshot.png'));
console.log('Saved screenshot.png');
} catch (error) {
throw new Error(`Could not save screenshot: ${error.message}`);
}
}
capture().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
Run it with the key in the environment. For example, in a POSIX shell:
APIFLASH_ACCESS_KEY=your_key_here node screenshot.js
The code checks the status before saving, drains an error response into a string for diagnosis, applies a request timeout, and uses pipeline so file-stream errors reach the caller. In a production service, route these errors through the application’s normal logging and response handling.
2. Build and encode the request
ApiFlash accepts GET query parameters or POST form data; its documented Node.js example uses GET. URLSearchParams safely encodes the key, URL, and other parameter values. Avoid building a query string by concatenating user-provided URLs, selectors, cookies, or header values.
The target url must include its scheme, such as https://. For example, example.com alone is not a complete target URL. The request goes to ApiFlash over HTTPS; the service then loads the target page to capture it.
Use POST form data if query parameters could be exposed in intermediary URL logs or exceed practical URL length limits. POST does not make a key safe to expose in browser code: send the request from your server and protect that server endpoint.
3. Choose capture options
Set parameters according to the page and output you need. ApiFlash documents these controls:
| Parameter | Purpose and notes |
|---|---|
format |
Image format: jpeg (default), png, or webp. Match the file extension to the selected format. |
width, height |
Viewport dimensions; documented defaults are 1920 by 1080. height is ignored with full-page capture. |
full_page |
Set to true to capture the full page rather than just the viewport. |
wait_until |
Readiness condition: dom_loaded, page_loaded, or network_idle. The default is network_idle. |
wait_for |
CSS selector to wait for before capturing. Capture is aborted if it does not appear within 15 seconds. |
delay |
Fixed wait, up to 10 seconds. ApiFlash recommends readiness conditions or wait_for where possible. |
fresh |
Set to true to request a new capture rather than a cached image. |
quality, scale_factor |
Adjust image quality or pixel scale. Check the current documentation for format-specific behavior and limits. |
crop, element |
Capture a cropped region or a selected page element; encode selector values with URLSearchParams. |
thumbnail_width |
Request a thumbnail width for output. |
For example, change the values in the Node.js sample to capture a 1365 by 900 viewport in WebP, wait for a page-specific element, and avoid full-page mode:
const params = new URLSearchParams({
access_key: process.env.APIFLASH_ACCESS_KEY,
url: 'https://example.com/products',
format: 'webp',
width: '1365',
height: '900',
wait_until: 'page_loaded',
wait_for: '.product-grid'
});
Write this response to screenshot.webp to keep the filename consistent with the requested format. For a long page, use full_page: 'true'; the viewport height no longer controls the captured page height. For a page that never becomes network-idle because of long-lived connections, try a more suitable documented readiness condition or wait for a specific element instead. WebP output is subject to documented maximum dimensions after scaling and an overall pixel limit. Check ApiFlash’s current documentation before relying on edge dimensions.
4. Handle response formats and status codes
The default successful response contains image bytes, with Content-Type and Content-Length headers. Do not assume every response body is an image: non-success responses must be handled before opening the destination file.
ApiFlash also documents response_type=json, which returns JSON containing a screenshot URL; that mode can also return extracted HTML or text. Parse JSON as JSON and handle its status and error details instead of piping it into an image file.
| HTTP status | Documented meaning | What to check |
|---|---|---|
| 400 | Invalid parameters or URL cannot be captured | Check parameter names and values, URL encoding, the complete target URL, and whether the target is reachable. |
| 401 | Invalid or revoked access key | Check the server environment variable and rotate or replace the key if needed. |
| 402 | Monthly quota exhausted | Review quota and plan usage before retrying. |
| 403 | Requested feature unsupported by the current plan | Check the feature’s plan availability and change the request or plan. |
| 429 | Too many requests | Reduce concurrency, respect rate limits, and retry with backoff where appropriate. |
| 500 | Capture failure | Retry selectively and inspect whether the target page or requested capture settings are causing the failure. |
These meanings come from ApiFlash’s documentation. Preserve response headers and useful error details in server logs, while avoiding logging access keys or sensitive cookies.
5. Use cURL, Python, or Node.js from your application
cURL
This saves the default image response to a file. Change the extension and format together if selecting another image format.
curl -G "https://api.apiflash.com/v1/urltoimage" \
--data-urlencode "access_key=$APIFLASH_ACCESS_KEY" \
--data-urlencode "url=https://example.com" \
--data-urlencode "format=png" \
--data-urlencode "full_page=true" \
--output screenshot.png
Python
This example uses the third-party requests package. Install it with python -m pip install requests, then stream the response to avoid holding a large image entirely in memory.
import os
import requests
key = os.environ["APIFLASH_ACCESS_KEY"]
params = {
"access_key": key,
"url": "https://example.com",
"format": "png",
"full_page": "true",
}
with requests.get(
"https://api.apiflash.com/v1/urltoimage",
params=params,
stream=True,
timeout=(10, 90),
) as response:
response.raise_for_status()
with open("screenshot.png", "wb") as image_file:
for chunk in response.iter_content(chunk_size=64 * 1024):
if chunk:
image_file.write(chunk)
Node.js
The built-in https and fs implementation in section 1 is the complete Node.js example. It uses GET, URLSearchParams, a streamed file write, and checks for non-200 responses before saving. ApiFlash also accepts POST form data; use a supported HTTP client if your application needs that request method.
6. Protect credentials and any public proxy
A server-side script can keep the access key private, but exposing a wrapper endpoint introduces another risk: anyone who can call it may use your quota to capture arbitrary URLs. ApiFlash’s Nginx guide recommends restricting allowed URLs, applying rate limits, and caching public screenshots when proxying the API. Apply equivalent controls in your application or edge layer:
- Allow only the target domains your product needs; validate hostnames after parsing the URL.
- Authenticate callers and impose per-user and global request limits.
- Set a maximum URL length, concurrency, and response size appropriate for your service.
- Cache repeat requests when freshness requirements allow it.
- Never forward a user-supplied access key or log the key, cookies, or authorization headers.
URL restrictions matter because a screenshot service that fetches caller-selected URLs can be abused to access destinations the caller should not control. Validate redirects as well as the initial hostname if your proxy is responsible for enforcing a destination allowlist.
7. Plan for rate limits, quota, and cost
ApiFlash documents a rate limit of 20 requests per second with a burst size of 400. Requests beyond the sustained rate may be delayed; requests beyond the burst can be terminated with HTTP 429. Use a bounded worker pool instead of launching an unbounded number of simultaneous captures. When retrying 429 or transient failures, use exponential backoff with jitter and a retry limit so a degraded service does not create a retry storm.
Quota headers include X-Quota-Limit, X-Quota-Remaining, and X-Quota-Reset; the documentation also describes a quota endpoint. Identical successful captures may be served from cache and do not count against the monthly quota. Confirm current accounting rules and read the response headers rather than estimating from request count alone.
The research dossier records ApiFlash’s published 2026 plan figures as Free: 100 screenshots/month; Lite: $7/month for 1,000; Medium: $35/month for 10,000; and Large: $180/month for 100,000. These are vendor-published and can change. Check the current ApiFlash plans before choosing a plan or publishing a budget estimate.
For cost control, monitor quota remaining, cache repeat captures where suitable, and set application-level limits. Large full-page images and high scale factors can increase transfer and storage needs even when the API’s billing rules are unchanged.
8. Troubleshoot common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| HTTP 400 | Malformed parameter, missing URL scheme, or target cannot be captured | Use a complete https:// URL, verify parameter spelling, and encode values with URLSearchParams. |
| HTTP 401 | Missing, invalid, or revoked key | Confirm APIFLASH_ACCESS_KEY is set in the process environment and replace a revoked key. |
| HTTP 402 | Monthly quota has run out | Check quota headers or the quota endpoint; reduce avoidable captures or select an appropriate plan. |
| HTTP 403 | A parameter requires a plan feature you do not have | Review plan availability for that option and remove it or change plans. |
| HTTP 429 | Rate or burst limit exceeded | Queue work, lower concurrency, and retry with bounded backoff. |
| HTTP 500 | Capture failed | Retry only a limited number of times; inspect target availability, page readiness, and capture dimensions. |
| Output file contains an error message | Error response was saved as if it were an image | Check status before opening the destination file; log the response body for non-200 results. |
| Output file is empty or truncated | File stream failed or process exited early | Await the write stream with pipeline and handle stream errors. |
| Capture misses late content | Page was captured before the relevant content appeared | Choose an appropriate wait_until, wait for a stable CSS selector, or use a short delay when necessary. |
| Capture waits too long | network_idle may not occur on pages with persistent requests |
Try page_loaded or a specific wait_for selector. |
| WebP request fails at large dimensions | Scaled width, height, or total pixels exceed documented limits | Reduce dimensions or scale factor and check current WebP limits in the API documentation. |
9. Performance and reliability checklist
- Use a readiness condition tied to the page rather than a long fixed delay where possible.
- Choose viewport dimensions and scale factor based on the actual output need; excessive dimensions increase image size and processing work.
- Use full-page capture only when the whole document is needed.
- Bound concurrency, queue excess work, and record latency and status codes.
- Retry transient failures with a small retry limit and jitter; do not repeatedly retry invalid parameters, authentication errors, or exhausted quota.
- Use a cache when identical captures can be reused, and set
fresh=truewhen a new capture is specifically required. - Make file writes atomic in production if downstream code may read the image while it is being saved.
10. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF, and its parameter names also work with those used by other screenshot APIs to make switching easier. See the ScreenshotNeo site and API documentation.
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo returned HTTP ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
From a shell, the same request can be made with cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
Or in Python with requests:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
- Cookie and consent banners are accepted and removed, along with known newsletter popups and chat widgets, before the shot; each step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed. Response headers report the page verdict and billing status.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdffor Claude, Cursor, and other MCP clients. - The free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000; every feature is on every plan.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
FAQ
Can I call ApiFlash directly from a browser?
A browser request would expose the access key to users. Put the call behind your server and enforce authentication, destination restrictions, and rate limits.
Does full-page mode use the height parameter?
ApiFlash documents that height is ignored for full-page capture. Use width and full-page mode for the page-length image.
When should I set fresh=true?
Use it when the result must come from a new capture rather than an eligible cached image. Repeated fresh captures can use more quota than reusable cached results.
Can the response be HTML or text instead of an image?
The documented JSON response mode can include a screenshot URL and supports extracted HTML or text. Select the relevant response mode and parse the response according to its content type.
Sources
- ApiFlash Screenshot API Documentation — endpoint, request methods, Node.js sample, options, response handling, errors, rate limits, and quota.
- ApiFlash website — published plans and pricing, which should be checked again before publication.
- ApiFlash Nginx proxy guide — API key concealment, URL restrictions, rate limiting, and caching guidance.


