Free tools Windows power users keep installed
One-click scans. No signup required.
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
To add a screenshot API to Express, create a server-side route that validates a requested URL, sends it and any capture options to a screenshot provider, then returns the provider’s image or PDF bytes with the correct content type. Keep the API key on the server. Use a POST request when you need advanced options such as custom CSS, PDF settings, or geolocation.
What the Express route does
An Express app does not need to launch or manage Chromium when it delegates rendering to a hosted screenshot API. The route receives a request from your application, validates its inputs, calls the provider, and streams the resulting bytes back to its caller.
- Install Express and the provider’s Node.js package, if you choose to use its SDK.
- Store the provider API key in a server-side environment variable.
- Validate and, where appropriate, restrict the requested URL and capture options.
- Call the screenshot endpoint and handle provider errors.
- Set the response content type and send the returned bytes.
The result is an image or PDF response from your own Express endpoint. Your application can also add its own authorization, caching, and usage controls around the upstream request.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsChoose an integration: SDK or direct HTTP
The official Screenshot API materials list the @screenshot-api/js SDK and an Express-specific integration using screenshotapi-to. Install the package that matches the provider and code path you intend to use; the SDK names are not interchangeable.
#1 Best Overall
npm install express @screenshot-api/jsis the install command listed on the official framework and SDK pages.npm install express screenshotapi-tois the command listed in the ScreenshotAPI Express integration.- Direct HTTP avoids coupling the route to an SDK and follows the documented REST endpoints. Use the provider’s current endpoint and response format from its API documentation.
The available materials document the endpoint paths and request behavior, but do not provide enough SDK method signatures to give a verified, runnable SDK example here. The direct HTTP version below makes the request contract explicit. The API paths shown are those documented by Screenshot API; use its documentation to confirm account-specific details before deployment.
Build a direct-HTTP Express route
Install dependencies and set the key
For a minimal JavaScript project, install Express:
npm install express
Set the API key in the server process environment as SCREENSHOTAPI_KEY. Do not put it in frontend JavaScript, a public repository, or a URL query string. The provider documentation recommends the Authorization: Bearer header or the X-API-Key header.
Runnable server example
This example uses Node.js with built-in fetch (available in current Node.js releases) and returns binary content. It passes basic capture options from the Express query string and forwards the provider’s content type where available. Set SCREENSHOT_API_BASE to the provider’s documented base URL for your account; the provider’s API documentation specifies endpoint paths but does not provide a hostname, so the example deliberately requires it as configuration rather than guessing one.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallimport express from 'express';
const app = express();
const apiKey = process.env.SCREENSHOTAPI_KEY;
const apiBase = process.env.SCREENSHOT_API_BASE;
if (!apiKey || !apiBase) {
throw new Error('Set SCREENSHOTAPI_KEY and SCREENSHOT_API_BASE');
}
function positiveInteger(value, fallback, name) {
if (value === undefined) return fallback;
if (typeof value !== 'string' || !/^d+$/.test(value)) {
throw new Error(`${name} must be a positive integer`);
}
const number = Number(value);
if (!Number.isSafeInteger(number) || number < 1) {
throw new Error(`${name} must be a positive integer`);
}
return number;
}
app.get('/api/screenshot', async (req, res) => {
if (typeof req.query.url !== 'string' || !req.query.url) {
return res.status(400).json({ error: 'Provide one url query parameter.' });
}
let target;
try {
target = new URL(req.query.url);
if (!['http:', 'https:'].includes(target.protocol)) {
return res.status(400).json({ error: 'Only http and https URLs are allowed.' });
}
} catch {
return res.status(400).json({ error: 'The url must be a valid absolute URL.' });
}
let width;
let height;
try {
width = positiveInteger(req.query.width, 1280, 'width');
height = positiveInteger(req.query.height, 800, 'height');
} catch (error) {
return res.status(400).json({ error: error.message });
}
const format = typeof req.query.format === 'string' ? req.query.format : 'png';
if (!['png', 'jpeg', 'webp', 'pdf'].includes(format)) {
return res.status(400).json({ error: 'format must be png, jpeg, webp, or pdf.' });
}
const upstreamUrl = new URL('/api/v1/screenshot', apiBase);
const payload = {
url: target.href,
format,
viewport: { width, height },
fullPage: req.query.fullPage === 'true'
};
try {
const upstream = await fetch(upstreamUrl, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(90000)
});
if (!upstream.ok) {
const detail = await upstream.text();
const status = [400, 401, 422, 429, 502].includes(upstream.status)
? upstream.status : 502;
return res.status(status).json({
error: 'Screenshot provider request failed.',
providerStatus: upstream.status,
detail: detail.slice(0, 1000)
});
}
const contentType = upstream.headers.get('content-type') ||
(format === 'pdf' ? 'application/pdf' : `image/${format}`);
const bytes = Buffer.from(await upstream.arrayBuffer());
res.set('Content-Type', contentType);
res.set('Cache-Control', 'private, max-age=60');
return res.send(bytes);
} catch (error) {
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
return res.status(504).json({ error: 'Screenshot request timed out.' });
}
console.error('Screenshot route failed:', error);
return res.status(502).json({ error: 'Could not reach the screenshot provider.' });
}
});
app.listen(3000, () => console.log('Listening on http://localhost:3000'));
Save the file as an ES module (for example, use "type": "module" in package.json) or adapt the imports for CommonJS. The request body uses the documented POST configuration shape: URL, output format, viewport, and full-page flag. Confirm exact field nesting and response behavior against the provider’s current API documentation before relying on this as a production contract.
Try the route
Start the server with the required environment variables set, then request a capture:
curl --get 'http://localhost:3000/api/screenshot'
--data-urlencode 'url=https://example.com'
--data-urlencode 'width=1280'
--data-urlencode 'height=800'
--data-urlencode 'format=png'
--data-urlencode 'fullPage=true'
--output page.png
For a PDF, change the format to pdf and use an output filename such as page.pdf. The route forwards the upstream content type rather than treating every successful response as a PNG.
Validate inputs and protect the route
Accepting an arbitrary URL turns your server into a proxy. URL syntax validation alone is not sufficient for a public service: a valid URL could point at an internal host or a destination your application should not fetch.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →- Require an absolute URL and allow only
httpandhttps. - For public-facing routes, use an allowlist of permitted hostnames when the product does not need arbitrary sites.
- Reject loopback, private-network, link-local, and internal service addresses, including alternate IP representations. Apply the policy to resolved destinations and redirects, not only the original hostname.
- Limit width, height, delay, timeout, and full-page requests to prevent unexpectedly expensive or long renders.
- Authenticate and rate-limit your own endpoint. Do not expose the provider key to clients.
- Set request and response size limits appropriate to your application, especially if clients can request PDFs or full-page captures.
The provider’s API key authenticates your upstream request; it does not decide which users may call your Express route.
Rank #3
Pass viewport, wait, and output options
The API documentation lists these capture parameters. Basic settings can be sent to GET /api/v1/screenshot as query parameters or included in a JSON configuration sent to POST /api/v1/screenshot. POST is the practical choice for complex options and is required for several advanced controls.
| Need | Documented options | Notes |
|---|---|---|
| Image or document output | format: png, jpeg, webp, or pdf |
Return the provider’s content type. Do not force an image MIME type for PDF output. |
| Viewport and page extent | Viewport width and height, fullPage, deviceScaleFactor |
A viewport-sized capture and a full-page capture are different outputs. Full-page captures may take longer or produce larger files. |
| Wait for rendering | waitUntil, waitForSelector, delayMs, timeoutMs |
Choose a readiness signal that matches the page; a fixed delay is simple but can waste time or still be too short. |
| Target one region | selector |
Use when only a specific element is needed. A selector that does not match is documented as a 422 error. |
| Appearance and cleanup | darkMode, blockAds, blockCookieBanners |
Use only if the resulting appearance is appropriate for the intended capture. |
| Image quality and cache | quality, cache, cacheTTL, staleTTL |
Quality is relevant to supported lossy formats. Cache settings trade freshness for reuse. |
| Advanced page changes | css, js, hideSelectors |
POST-only according to the API documentation. Treat injected code and styles as privileged inputs. |
| Regional rendering and PDF layout | geolocation, timezoneId, locale, pdf |
POST-only. Specify these when the page’s localized or document layout matters. |
| Response delivery | redirect |
GET can return JSON by default; redirect=1 is documented for redirecting to an image or PDF. |
For basic GET requests, the API accepts query-string configurations. For advanced options—CSS, JavaScript, hidden selectors, geolocation, locale, timezone, and PDF controls—the documentation specifies POST. Keep the provider’s exact schema as the source of truth for field names and nesting when expanding the route.
Return the right response and cache deliberately
The Express integration guide demonstrates setting Content-Type, Cache-Control, and an x-credits-remaining response header, then sending Buffer.from(shot.image). With direct HTTP, read the upstream bytes and content type as in the example. Do not assume that an SDK’s shot.image is a Buffer without checking that SDK’s response type.
Recommended Free Tools
Cache only when the target page and request options make reuse safe. A cache key should include the normalized URL and every option that changes the output, such as viewport, format, locale, and full-page mode. Do not share cached captures across callers if the result may include private or authenticated page content. The provider documents cache, cacheTTL, and staleTTL controls; these are distinct from HTTP caching at your Express endpoint.
Rank #4
Handle errors, timeouts, and retries
Map upstream failures to useful responses without returning secrets or unbounded provider error bodies. The API documentation identifies these statuses and cases:
| Status | Documented meaning | Express handling |
|---|---|---|
| 400 | Invalid request | Check required fields and option types before calling upstream; return a concise client error. |
| 401 | Unauthorized | Check that the server has the correct key and that it is sent in the required header. |
| 422 | Selector not found | Check the selector and whether the page has loaded the expected element. |
| 429 | Rate-limited or quota-exceeded | Slow requests, apply backoff where appropriate, and inspect account quota. |
| 502 | Render failure | Return a controlled upstream error and allow a user or background job to retry selectively. |
Set a timeout that fits both your provider’s rendering allowance and your Express infrastructure’s request limits. A retry can help with transient network or render failures, but blindly retrying every 4xx response wastes time and may amplify load. For non-idempotent work or paid captures, use a request identity or deduplication strategy if the provider supports it; the available API details do not establish an idempotency guarantee.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Capture many URLs with batch jobs
For a workload involving multiple pages, the documented endpoint is POST /api/v1/screenshot/batch. It returns a batch ID; use GET /api/v1/batch/:batchId to poll status or GET /api/v1/batch/:batchId/stream for server-sent event updates. Persist the batch ID and expose progress through your app rather than holding one Express request open for a long batch.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Batch processing introduces its own concerns: validate every URL, cap the number of items your application accepts, retain the relationship between each input and its result, and define what the caller sees when only some captures fail. The endpoint’s exact per-batch limits and response schema are not stated in the available API details; consult the provider’s current documentation rather than assuming a maximum.
Hosted API or self-managed browser?
A hosted screenshot API replaces the work of installing and maintaining a browser binary and coordinating browser processes with an HTTP request per screenshot. Self-managed browser automation gives you more direct control over the rendering environment, but your application team owns browser deployment, memory use, process isolation, concurrency, and recovery from crashes.
Make the choice based on your deployment and privacy constraints. A hosted API sends target URLs and capture configuration to the provider, so assess whether that is acceptable for your data. A self-hosted browser keeps rendering in your infrastructure but requires operational ownership. Compare actual request latency and total cost for your own page mix; the available materials do not establish universal latency, memory, or cost benchmarks for either approach.
Or skip the browser setup
If you prefer a one-call service rather than wiring a provider route, ScreenshotNeo is a website screenshot API and MCP server. A GET request returns a PNG, JPEG, WebP, or PDF. Its API parameters also use names other screenshot APIs use, which can make switching easier. The request below saves a WebP capture; see the ScreenshotNeo API documentation for parameters and response details.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for ScreenshotNeo’s free plan.
Troubleshooting checklist
- 400 from your route: Confirm the caller sent one string URL, an absolute address, an allowed protocol, and valid numeric dimensions.
- 401 upstream: Confirm
SCREENSHOTAPI_KEYis set in the running server environment and the bearer header is correctly formed. Restart the process after changing environment configuration. - 429 upstream: Reduce request bursts, queue work, and check the account’s quota or rate limits. Do not retry immediately in a tight loop.
- 422 selector error: Verify the selector against the rendered page and wait for the element when it appears asynchronously.
- 502 or timeout: Check whether the target site is reachable and whether its load time exceeds your configured timeout. Consider a more appropriate wait condition; increasing a timeout can increase caller latency.
- Browser displays garbage instead of an image: Check that the route sends raw bytes, not JSON-encoded binary, and that it forwards the actual
Content-Type. - Unexpectedly stale output: Review both provider cache controls and your own HTTP cache key and lifetime.
- Works locally but not in production: Check outbound network access, environment variables, reverse-proxy request timeouts, and whether the deployment permits the required response size.
Frequently Asked Questions
Can an Express route return a PDF as well as an image?
Yes. Request the PDF format and return the provider’s PDF content type and bytes; the documented formats include PDF.
Should I use GET or POST for screenshot options?
Use GET for query-string configurations; use POST JSON for complex configurations and the documented POST-only options.
Can I use a batch endpoint for background captures?
Yes. The documented batch flow returns a batch ID that can be polled or followed through its stream endpoint.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

