Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

  1. Install Express and the provider’s Node.js package, if you choose to use its SDK.
  2. Store the provider API key in a server-side environment variable.
  3. Validate and, where appropriate, restrict the requested URL and capture options.
  4. Call the screenshot endpoint and handle provider errors.
  5. 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose 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.

  • npm install express @screenshot-api/js is the install command listed on the official framework and SDK pages.
  • npm install express screenshotapi-to is 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Require an absolute URL and allow only http and https.
  • 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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_KEY is 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.