arrow_back Back to blog
Use Cases March 1, 2026 9 min read

5 Ways to Use Screenshot APIs in Your Application

Screenshots are not just for documentation or screen recording. In modern web development, screenshot APIs have become infrastructure — quietly powering everything from social sharing previews to automated visual regression pipelines. If you are building a SaaS, running a web agency, or maintaining a large web application, there is a good chance you can benefit from programmatic screenshot generation.

In this post, we cover five practical ways to integrate a screenshot API into your application, with real code examples using len.sh. By the end, you will have concrete patterns you can adapt for your own projects.

The five use cases:

  1. Dynamic OG image generation
  2. Website monitoring and change detection
  3. PDF-style document generation
  4. Rich link previews
  5. Visual regression testing

1. Dynamic OG Image Generation

When someone shares a link on Twitter, LinkedIn, or Slack, the platform fetches the og:image metadata and renders a preview card. A compelling, dynamic image can dramatically increase click-through rates. Static images are limiting — you want images that reflect the actual content of each page.

The recommended approach is the dedicated /v1/og endpoint. It generates branded 1200x630 Open Graph images directly from structured parameters — no HTML template to build or host. You pass in a title, subtitle, badge, URL, brand name, brand color, and theme, and it returns a ready-to-use PNG.

Example: Generating a branded OG image with /v1/og

curl "https://api.len.sh/v1/og?title=Hello+World&subtitle=A+great+article&badge=Blog&url=yoursite.com&brand_name=YourBrand&brand_color=%234F46E5&theme=light&api_key=YOUR_API_KEY" \
  --output og-image.png

In a Node.js server (e.g. Next.js API route or Express endpoint), you can generate this on demand:

// pages/api/og.js (Next.js)
export default async function handler(req, res) {
  const { title, subtitle } = req.query;

  const ogUrl = new URL("https://api.len.sh/v1/og");
  ogUrl.searchParams.set("title", title);
  ogUrl.searchParams.set("subtitle", subtitle || "");
  ogUrl.searchParams.set("badge", "Blog");
  ogUrl.searchParams.set("url", "yoursite.com");
  ogUrl.searchParams.set("brand_name", "YourBrand");
  ogUrl.searchParams.set("brand_color", "#4F46E5");
  ogUrl.searchParams.set("theme", "light");
  ogUrl.searchParams.set("api_key", process.env.LENSH_API_KEY);

  const response = await fetch(ogUrl.toString());
  const buffer = await response.arrayBuffer();

  res.setHeader("Content-Type", "image/png");
  res.setHeader("Cache-Control", "public, max-age=86400");
  res.send(Buffer.from(buffer));
}

Then in your page’s <head>:

<meta property="og:image" content="https://yoursite.com/api/og?title=Hello+World&subtitle=A+great+article" />

The /v1/og endpoint handles layout, typography, and branding for you. No HTML template to maintain and no viewport configuration to get right.

If you need full pixel-level control over the design, you can still use the /v1/screenshot endpoint with an HTML template URL or the html parameter to render arbitrary markup at 1200x630. The dedicated OG endpoint covers the vast majority of use cases with far less setup.


2. Website Monitoring and Change Detection

Visual monitoring is one of the most underrated applications of screenshot APIs. Traditional uptime monitors tell you when a site is down, but they cannot tell you when a page quietly breaks — a misaligned layout, a missing hero image, or defaced content.

By periodically capturing screenshots and comparing them, you can catch visual regressions before users do.

Example: Scheduled screenshot + image comparison

Using node-cron and pixelmatch for comparison:

import cron from "node-cron";
import fetch from "node-fetch";
import fs from "fs/promises";
import { PNG } from "pngjs";
import pixelmatch from "pixelmatch";

const API_KEY = process.env.LENSH_API_KEY;
const TARGET_URL = "https://yoursite.com";
const BASELINE_PATH = "./baseline.png";
const CURRENT_PATH = "./current.png";
const DIFF_THRESHOLD = 0.05; // 5% pixel difference threshold

async function captureScreenshot(outputPath) {
  const url = `https://api.len.sh/v1/screenshot?url=${encodeURIComponent(TARGET_URL)}&width=1280&height=800&format=png&api_key=${API_KEY}`;
  const res = await fetch(url);
  const buffer = await res.arrayBuffer();
  await fs.writeFile(outputPath, Buffer.from(buffer));
}

async function compareScreenshots() {
  const baselineData = PNG.sync.read(await fs.readFile(BASELINE_PATH));
  const currentData = PNG.sync.read(await fs.readFile(CURRENT_PATH));

  const { width, height } = baselineData;
  const diff = new PNG({ width, height });

  const numDiffPixels = pixelmatch(
    baselineData.data,
    currentData.data,
    diff.data,
    width,
    height,
    { threshold: 0.1 }
  );

  const diffRatio = numDiffPixels / (width * height);
  return { diffRatio, numDiffPixels };
}

// Run every 15 minutes
cron.schedule("*/15 * * * *", async () => {
  console.log("Running visual check...");
  await captureScreenshot(CURRENT_PATH);

  const { diffRatio } = await compareScreenshots();
  if (diffRatio > DIFF_THRESHOLD) {
    console.error(`Visual change detected! Diff ratio: ${(diffRatio * 100).toFixed(2)}%`);
    // Send alert via Slack, PagerDuty, email, etc.
  } else {
    console.log(`All clear. Diff: ${(diffRatio * 100).toFixed(2)}%`);
    // Update baseline
    await fs.copyFile(CURRENT_PATH, BASELINE_PATH);
  }
});

For a first run, capture your baseline:

curl "https://api.len.sh/v1/screenshot?url=https://yoursite.com&width=1280&height=800&format=png&api_key=YOUR_API_KEY" \
  --output baseline.png

This is a lightweight alternative to full-blown visual monitoring services — and you own the comparison logic completely.


3. PDF-Style Document Generation

Traditional PDF generation in Node.js usually involves libraries like pdfkit, puppeteer, or wkhtmltopdf. These tools are powerful but come with trade-offs: complex APIs, layout quirks, or heavy dependencies.

If your report, invoice, or dashboard already renders correctly as a web page, screenshotting it to PDF is the most reliable approach. What you see in the browser is exactly what you get in the PDF.

Example: Generating a PDF invoice

curl "https://api.len.sh/v1/screenshot?url=https://yourapp.com/invoices/inv-1042&format=pdf&full_page=true&api_key=YOUR_API_KEY" \
  --output invoice-1042.pdf

The full_page=true parameter captures the entire scrollable page, not just the viewport — essential for long documents.

In an Express endpoint that streams the PDF to the client:

import express from "express";
import fetch from "node-fetch";

const app = express();

app.get("/invoices/:id/pdf", async (req, res) => {
  const invoiceUrl = `https://yourapp.com/invoices/${req.params.id}`;

  const screenshotUrl = new URL("https://api.len.sh/v1/screenshot");
  screenshotUrl.searchParams.set("url", invoiceUrl);
  screenshotUrl.searchParams.set("format", "pdf");
  screenshotUrl.searchParams.set("full_page", "true");
  screenshotUrl.searchParams.set("width", "1200");
  screenshotUrl.searchParams.set("api_key", process.env.LENSH_API_KEY);

  const upstream = await fetch(screenshotUrl.toString());

  res.setHeader("Content-Type", "application/pdf");
  res.setHeader(
    "Content-Disposition",
    `attachment; filename="invoice-${req.params.id}.pdf"`
  );

  upstream.body.pipe(res);
});

You can use the delay parameter to wait for dynamic content to load before capturing. For example, if your charts render after a JavaScript animation:

curl "https://api.len.sh/v1/screenshot?url=https://yourapp.com/reports/q4&format=pdf&full_page=true&delay=2000&api_key=YOUR_API_KEY" \
  --output q4-report.pdf

This waits 2000ms after page load before capturing, giving async data fetches and animations time to complete.


Slack, Notion, and Twitter all show rich preview cards when you paste a URL. You can build the same experience in your own application — a chatroom, a bookmarking tool, a content aggregator, or any app where users share links.

The flow is: user pastes a URL, you call the screenshot API in the background, cache the result, and display the thumbnail alongside the link title and description.

Example: Express endpoint for link preview images

import express from "express";
import fetch from "node-fetch";
import NodeCache from "node-cache";

const app = express();
const cache = new NodeCache({ stdTTL: 3600 }); // 1-hour cache

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

  const cacheKey = `preview:${url}`;
  const cached = cache.get(cacheKey);
  if (cached) {
    res.setHeader("Content-Type", "image/webp");
    res.setHeader("X-Cache", "HIT");
    return res.send(cached);
  }

  try {
    const screenshotUrl = new URL("https://api.len.sh/v1/screenshot");
    screenshotUrl.searchParams.set("url", url);
    screenshotUrl.searchParams.set("width", "1280");
    screenshotUrl.searchParams.set("height", "720");
    screenshotUrl.searchParams.set("format", "webp");
    screenshotUrl.searchParams.set("api_key", process.env.LENSH_API_KEY);

    const upstream = await fetch(screenshotUrl.toString());
    const buffer = Buffer.from(await upstream.arrayBuffer());

    cache.set(cacheKey, buffer);

    res.setHeader("Content-Type", "image/webp");
    res.setHeader("Cache-Control", "public, max-age=3600");
    res.setHeader("X-Cache", "MISS");
    res.send(buffer);
  } catch (err) {
    res.status(500).json({ error: "Failed to capture preview" });
  }
});

app.listen(3000);

WebP format is a good default here — it gives excellent compression at high quality, which keeps your preview images fast to load. On the client side, you can call /preview?url=https://example.com to get a thumbnail image for any URL.

For a production setup, replace the in-process NodeCache with Redis so previews persist across deployments and are shared across instances.


5. Visual Regression Testing

Visual regression testing means automatically comparing your UI before and after a code change to catch unintended visual breakage. This is especially valuable for component libraries, design systems, and apps with complex CSS.

The screenshot API fits naturally into a CI pipeline: capture baseline screenshots before the change, deploy, capture again, then diff.

Example: Visual regression in a Vitest test

// tests/visual/homepage.test.js
import { describe, it, expect } from "vitest";
import fetch from "node-fetch";
import { PNG } from "pngjs";
import pixelmatch from "pixelmatch";
import fs from "fs/promises";
import path from "path";

const API_KEY = process.env.LENSH_API_KEY;
const BASE_URL = process.env.PREVIEW_URL || "http://localhost:3000";
const SNAPSHOTS_DIR = path.resolve("tests/visual/snapshots");

async function screenshot(pagePath, width = 1280, height = 800) {
  const url = `${BASE_URL}${pagePath}`;
  const apiUrl = new URL("https://api.len.sh/v1/screenshot");
  apiUrl.searchParams.set("url", url);
  apiUrl.searchParams.set("width", String(width));
  apiUrl.searchParams.set("height", String(height));
  apiUrl.searchParams.set("format", "png");
  apiUrl.searchParams.set("api_key", API_KEY);

  const res = await fetch(apiUrl.toString());
  return Buffer.from(await res.arrayBuffer());
}

describe("Visual regression: Homepage", () => {
  it("matches the baseline snapshot", async () => {
    const snapshotPath = path.join(SNAPSHOTS_DIR, "homepage.png");
    const currentBuffer = await screenshot("/");

    let baselineBuffer;
    try {
      baselineBuffer = await fs.readFile(snapshotPath);
    } catch {
      // No baseline yet — write it and pass
      await fs.mkdir(SNAPSHOTS_DIR, { recursive: true });
      await fs.writeFile(snapshotPath, currentBuffer);
      return;
    }

    const baseline = PNG.sync.read(baselineBuffer);
    const current = PNG.sync.read(currentBuffer);
    const { width, height } = baseline;
    const diff = new PNG({ width, height });

    const numDiffPixels = pixelmatch(
      baseline.data,
      current.data,
      diff.data,
      width,
      height,
      { threshold: 0.1 }
    );

    const diffRatio = numDiffPixels / (width * height);
    expect(diffRatio).toBeLessThan(0.01); // less than 1% difference
  });
});

In CI, you would:

  1. Check out the base branch, build the app, capture baseline screenshots
  2. Check out the feature branch, build, capture current screenshots
  3. Run vitest — tests pass if visual differences are under the threshold

You can extend this to any number of pages. The len.sh API handles the browser, rendering, and network — your test just needs to do the diff.


Bonus: Thumbnail Galleries

If you are building a site directory, portfolio showcase, or curated link collection, you can use the screenshot API to automatically generate thumbnails for every entry. Kick off screenshot jobs when a new site is added, store the results in object storage (S3, R2, etc.), and serve them as thumbnails.

async function generateThumbnail(siteUrl) {
  const apiUrl = `https://api.len.sh/v1/screenshot?url=${encodeURIComponent(siteUrl)}&width=800&height=600&format=webp&api_key=${process.env.LENSH_API_KEY}`;
  const res = await fetch(apiUrl);
  return res.arrayBuffer();
}

Pair this with a CDN and you have an auto-updating gallery of site screenshots that scales to thousands of entries without any manual work.


Getting Started with len.sh

All the examples above use the len.sh screenshot API. To get started:

  1. Sign up at len.sh and get your API key from the dashboard
  2. Make your first screenshot request:
curl "https://api.len.sh/v1/screenshot?url=https://example.com&api_key=YOUR_API_KEY" \
  --output screenshot.png

The core screenshot parameters:

ParameterDescriptionDefault
urlThe URL to screenshot (required)
widthViewport width in pixels1280
heightViewport height in pixels800
formatOutput format: png, jpeg, webp, pdfpng
full_pageCapture full scrollable page (true/false)false
delayWait in milliseconds after page load0
api_keyYour API key (required)

For branded OG images, use the /v1/og endpoint with these parameters:

ParameterDescriptionDefault
titleMain heading text (required)
subtitleSecondary text below the title
badgeSmall label displayed above the title
urlURL displayed on the image
brand_nameYour brand name
brand_colorHex color for brand accent (e.g. #4F46E5)
themeColor theme: light or darklight
api_keyYour API key (required)

For full documentation, visit the len.sh docs.


Conclusion

Screenshot APIs have moved well beyond their “take a picture of a website” origins. They are now practical building blocks for dynamic image generation, monitoring infrastructure, document pipelines, link previews, and testing automation.

The five patterns above are starting points — most production implementations layer on caching, error handling, queuing, and storage. But the core integration is always just an HTTP call.

Pick the use case that fits your current problem, adapt the code example for your stack, and you can have a working integration in an afternoon. The browser management, rendering, and scaling are handled for you.

If you build something interesting with len.sh, we would love to hear about it. Reach out on GitHub or via the support page.

Try len.sh for free

Start capturing screenshots with a simple API call. No credit card required.