Screenshot API for Node.js: fetch, axios & Serverless Integration

Capture screenshots, scrape web pages, and generate PDFs from Node.js using the native fetch API or axios. Works with Express, Fastify, NestJS, and serverless platforms — no Puppeteer or browser binaries required.

Start Free — 200 captures/moView Docs

Screenshot API for Node.js — Native fetch Quickstart

Node.js 18 and later includes the native Fetch API, eliminating the need for axios or node-fetch for basic HTTP calls. Here is a complete screenshot function using only built-in Node.js APIs:

const { writeFile } = require('fs/promises');

async function captureScreenshot(url, apiKey) {
  const params = new URLSearchParams({
    url,
    full_page: 'true',
    format: 'jpeg',
    block_ads: 'true',
    block_cookies: 'true'
  });

  const response = await fetch(
    `https://api.snapapi.pics/v1/screenshot?${params}`,
    {
      headers: { 'X-Api-Key': apiKey },
      signal: AbortSignal.timeout(30_000)
    }
  );

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`SnapAPI ${response.status}: ${error}`);
  }

  return Buffer.from(await response.arrayBuffer());
}

// Usage
const imageBuffer = await captureScreenshot('https://example.com', process.env.SNAPAPI_KEY);
await writeFile('screenshot.jpg', imageBuffer);
console.log(`Saved ${imageBuffer.length} bytes`);

AbortSignal.timeout(30_000) cancels the request after 30 seconds without requiring a manual AbortController. For video recording requests that take longer, increase the timeout to 120 seconds. The arrayBuffer() method returns raw binary data that Buffer.from() converts to a Node.js Buffer for file writing or streaming.

Using axios for Older Node.js Versions

For Node.js 14 and 16, or projects that prefer axios for its interceptor and retry support:

const axios = require('axios');

async function captureScreenshot(url, apiKey) {
  const response = await axios.get('https://api.snapapi.pics/v1/screenshot', {
    params: { url, full_page: true, format: 'jpeg', block_ads: true },
    headers: { 'X-Api-Key': apiKey },
    responseType: 'arraybuffer',
    timeout: 30000
  });
  return Buffer.from(response.data);
}

// With axios-retry for automatic backoff on transient failures:
const axiosRetry = require('axios-retry');
axiosRetry(axios, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => error.response?.status === 429 || error.response?.status >= 500
});

Set responseType: 'arraybuffer' to receive the binary image data rather than a string. The axios-retry middleware handles exponential backoff automatically, retrying on 429 rate limit and 5xx server error responses.

Express.js Integration: Screenshot Proxy Endpoint

Build a screenshot proxy in Express that adds your API key server-side and streams the result to the client. This pattern keeps your API key out of the browser while allowing frontend applications to request screenshots:

const express = require('express');
const app = express();

app.get('/screenshot', async (req, res) => {
  const { url } = req.query;
  if (!url) return res.status(400).json({ error: 'url required' });

  try {
    const params = new URLSearchParams({ url, full_page: 'true', format: 'jpeg' });
    const upstream = await fetch(`https://api.snapapi.pics/v1/screenshot?${params}`, {
      headers: { 'X-Api-Key': process.env.SNAPAPI_KEY },
      signal: AbortSignal.timeout(30_000)
    });

    if (!upstream.ok) return res.status(upstream.status).json({ error: 'Capture failed' });

    res.set('Content-Type', 'image/jpeg');
    res.set('Cache-Control', 'public, max-age=3600');
    upstream.body.pipeTo(new WritableStream({
      write(chunk) { res.write(chunk); },
      close() { res.end(); }
    }));
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000);

The response is streamed from SnapAPI directly to the Express response, avoiding buffering the full image in memory. The Cache-Control header tells browsers and CDNs to cache the screenshot for one hour, reducing repeat API calls for the same URL.

NestJS Integration with Injectable Service

In NestJS, wrap SnapAPI in an @Injectable() service and use HttpModule for dependency-injected HTTP calls:

@Injectable()
export class ScreenshotService {
  constructor(private readonly httpService: HttpService,
              private readonly config: ConfigService) {}

  async capture(url: string): Promise<Buffer> {
    const params = new URLSearchParams({ url, full_page: 'true', format: 'jpeg' });
    const response = await firstValueFrom(
      this.httpService.get(`https://api.snapapi.pics/v1/screenshot?${params}`, {
        headers: { 'X-Api-Key': this.config.get('SNAPAPI_KEY') },
        responseType: 'arraybuffer',
        timeout: 30000
      })
    );
    return Buffer.from(response.data);
  }
}

Inject ScreenshotService into any controller or other service. The ConfigService reads the API key from your NestJS configuration module, supporting environment-specific values in .env files or cloud secret managers.

Serverless Deployment: AWS Lambda, Vercel & Cloudflare Workers

SnapAPI is ideal for serverless environments because it eliminates the browser binary dependency that makes running Puppeteer on Lambda painful. Your serverless function makes a standard HTTPS request and returns the result — no cold start overhead from launching Chromium, no layer size limits to work around.

// AWS Lambda handler (Node.js 18+ runtime)
exports.handler = async (event) => {
  const url = event.queryStringParameters?.url;
  if (!url) return { statusCode: 400, body: 'Missing url' };

  const params = new URLSearchParams({ url, full_page: 'true', format: 'jpeg' });
  const res = await fetch(`https://api.snapapi.pics/v1/screenshot?${params}`, {
    headers: { 'X-Api-Key': process.env.SNAPAPI_KEY }
  });

  const imageData = Buffer.from(await res.arrayBuffer()).toString('base64');
  return {
    statusCode: 200,
    headers: { 'Content-Type': 'image/jpeg' },
    body: imageData,
    isBase64Encoded: true
  };
};

Lambda requires base64-encoded binary responses via API Gateway. The pattern works identically on Vercel Edge Functions and Cloudflare Workers using the standard Fetch API. Store your SnapAPI key in Lambda environment variables, Vercel environment settings, or Cloudflare Worker secrets.

Start for free at snapapi.pics — 200 captures per month, no credit card required. The official Node.js SDK at github.com/Sleywill/snapapi-js wraps all endpoints with full TypeScript support. Install with npm install snapapi-js.

Batch Screenshot Capture with Promise.all in Node.js

For batch jobs that process many URLs, use a concurrency-limited Promise pool to fire multiple requests in parallel without overwhelming the API rate limit:

async function captureAll(urls, concurrency = 5) {
  const results = [];
  const queue = [...urls];

  async function worker() {
    while (queue.length > 0) {
      const url = queue.shift();
      try {
        const buffer = await captureScreenshot(url, process.env.SNAPAPI_KEY);
        results.push({ url, success: true, bytes: buffer.length });
      } catch (err) {
        results.push({ url, success: false, error: err.message });
      }
    }
  }

  await Promise.all(Array.from({ length: concurrency }, worker));
  return results;
}

const urls = ["https://example.com", "https://github.com", "https://npmjs.com"];
const results = await captureAll(urls, 5);
console.log(results.filter(r => r.success).length, "succeeded");

This worker pool pattern maintains exactly concurrency parallel requests at all times. Each worker grabs the next URL from the queue when it finishes, so faster captures do not wait for slower ones. Failed captures are recorded in results without interrupting the batch.

PDF Generation from Node.js

The PDF endpoint accepts the same request structure as screenshot. For Express applications that need to serve PDF downloads, pipe the response directly to the client:

app.get("/invoice/:id", async (req, res) => {
  const invoiceUrl = `https://app.example.com/invoices/${req.params.id}`;
  const params = new URLSearchParams({ url: invoiceUrl });
  const upstream = await fetch("https://api.snapapi.pics/v1/pdf?" + params, {
    method: "POST",
    headers: {
      "X-Api-Key": process.env.SNAPAPI_KEY,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ url: invoiceUrl, page_size: "A4", print_background: true })
  });
  res.set("Content-Type", "application/pdf");
  res.set("Content-Disposition", `attachment; filename=invoice-${req.params.id}.pdf`);
  upstream.body.pipeTo(new WritableStream({
    write: chunk => res.write(chunk),
    close: () => res.end()
  }));
});

Setting Content-Disposition: attachment triggers a file download dialog in the browser. Use inline instead to display the PDF in the browser's built-in PDF viewer. The response is streamed directly from SnapAPI to the client, so invoice PDFs never touch your server's disk.

TypeScript Integration and Type Definitions

The official SnapAPI JavaScript SDK includes full TypeScript definitions. For projects that prefer a direct fetch approach, here is a typed wrapper compatible with TypeScript strict mode:

interface ScreenshotOptions {
  url: string;
  fullPage?: boolean;
  format?: "jpeg" | "png" | "webp";
  width?: number;
  height?: number;
  blockAds?: boolean;
  blockCookies?: boolean;
  delay?: number;
  waitFor?: string;
  device?: string;
}

async function captureScreenshot(opts: ScreenshotOptions, apiKey: string): Promise<Buffer> {
  const params = new URLSearchParams({
    url: opts.url,
    ...(opts.fullPage && { full_page: "true" }),
    ...(opts.format && { format: opts.format }),
    ...(opts.blockAds && { block_ads: "true" }),
  });
  const response = await fetch(`https://api.snapapi.pics/v1/screenshot?${params}`, {
    headers: { "X-Api-Key": apiKey },
    signal: AbortSignal.timeout(30_000),
  });
  if (!response.ok) throw new Error(`SnapAPI ${response.status}`);
  return Buffer.from(await response.arrayBuffer());
}

The TypeScript interface provides autocomplete and compile-time validation for all capture parameters. Add the remaining options — customCss, customJs, cookies, headers — to the interface as your integration grows.

Node.js SDK via npm

Install the official SDK with npm install snapapi-js. It wraps all six API endpoints — screenshot, scrape, extract, PDF, video, and analyze — with a consistent async interface and full TypeScript support. Free tier: 200 captures/month at snapapi.pics, no credit card required.

Rate Limiting and Webhook Callbacks in Node.js

For high-volume pipelines, read the X-RateLimit-Remaining response header after every SnapAPI call and pause your queue when it approaches zero. A simple token bucket implemented with a semaphore keeps concurrent requests inside the allowed burst window without triggering 429 errors. Pair this with the optional webhook_url parameter for long-running captures — SnapAPI will POST the result payload to your endpoint when rendering completes, freeing your Node.js process from blocking on the response entirely. This pattern is ideal for serverless deployments where function execution time is billed per millisecond.

SnapAPI Node.js SDK supports CommonJS and ESM module formats across Node 16 18 20 and 22 LTS releases with full TypeScript declaration files included for type-safe integration without additional type packages required.