Screenshot API TypeScript Guide 2026 — Full Type Safety & Next.js

Capture screenshots and generate PDFs from TypeScript applications with full type safety. Complete typed client, Next.js App Router, Cloudflare Workers, and Bun examples.

Get Free API Key

Typed SnapAPI Client in TypeScript

interface ScreenshotOptions {
  url: string;
  format?: 'png' | 'jpeg' | 'webp' | 'pdf';
  fullPage?: boolean;
  viewportWidth?: number;
  viewportHeight?: number;
  delay?: number;
  waitForSelector?: string;
  paperFormat?: 'A4' | 'A3' | 'Letter' | 'Legal';
  printBackground?: boolean;
}

interface ScrapeResult {
  title: string;
  content: string;
  html?: string;
  links: string[];
  og_title?: string;
  og_description?: string;
  og_image?: string;
  canonical_url?: string;
}

interface ExtractResult> {
  data: T;
  url: string;
  captured_at: string;
}

class SnapAPIClient {
  private readonly baseUrl = 'https://snapapi.pics';

  constructor(private readonly apiKey: string) {
    if (!apiKey) throw new Error('SnapAPI key required');
  }

  async screenshot(options: ScreenshotOptions): Promise {
    const params = new URLSearchParams({
      access_key: this.apiKey,
      url: options.url,
      format: options.format ?? 'png',
      full_page: options.fullPage ? '1' : '0',
      viewport_width: String(options.viewportWidth ?? 1280),
      viewport_height: String(options.viewportHeight ?? 800),
    });
    if (options.delay) params.set('delay', String(options.delay));
    if (options.waitForSelector) params.set('wait_for_selector', options.waitForSelector);
    if (options.paperFormat) params.set('paper_format', options.paperFormat);
    if (options.printBackground) params.set('print_background', '1');

    const res = await fetch(`${this.baseUrl}/screenshot?${params}`);
    if (!res.ok) {
      const text = await res.text();
      throw new Error(`SnapAPI ${res.status}: ${text}`);
    }
    return res.arrayBuffer();
  }

  async scrape(url: string, output: 'text' | 'markdown' | 'html' = 'markdown'): Promise {
    const params = new URLSearchParams({ access_key: this.apiKey, url, output });
    const res = await fetch(`${this.baseUrl}/scrape?${params}`);
    if (!res.ok) throw new Error(`SnapAPI scrape ${res.status}`);
    return res.json() as Promise;
  }

  async extract>(
    url: string,
    schema: T
  ): Promise> {
    const res = await fetch(`${this.baseUrl}/extract`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ access_key: this.apiKey, url, schema }),
    });
    if (!res.ok) throw new Error(`SnapAPI extract ${res.status}`);
    return res.json();
  }
}

const client = new SnapAPIClient(process.env.SNAPAPI_KEY!);

// Typed screenshot
const png = await client.screenshot({ url: 'https://example.com', fullPage: true });
const buffer = Buffer.from(png);

// Typed scrape
const page = await client.scrape('https://example.com');
console.log(page.title, page.og_image);

// Typed extract
const product = await client.extract('https://example.com/product', {
  name: 'string',
  price: 'number',
  inStock: 'boolean'
});
console.log(product.name, product.price, product.inStock);

Next.js App Router Integration

// app/api/screenshot/route.ts
import { NextRequest, NextResponse } from 'next/server';

const snapClient = new SnapAPIClient(process.env.SNAPAPI_KEY!);

export async function GET(request: NextRequest): Promise {
  const { searchParams } = request.nextUrl;
  const url = searchParams.get('url');
  const format = (searchParams.get('format') ?? 'png') as 'png' | 'pdf';

  if (!url) {
    return NextResponse.json({ error: 'url required' }, { status: 400 });
  }

  try {
    const buffer = await snapClient.screenshot({ url, format, fullPage: true });
    const contentType = format === 'pdf' ? 'application/pdf' : 'image/png';

    return new NextResponse(buffer, {
      headers: {
        'Content-Type': contentType,
        'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
      },
    });
  } catch (err) {
    return NextResponse.json({ error: String(err) }, { status: 502 });
  }
}

export const runtime = 'nodejs'; // or 'edge' for Edge Runtime

Cloudflare Workers Integration

// worker.ts — Cloudflare Workers
interface Env {
  SNAPAPI_KEY: string;
}

export default {
  async fetch(request: Request, env: Env): Promise {
    const { searchParams } = new URL(request.url);
    const url = searchParams.get('url');
    if (!url) return new Response('url required', { status: 400 });

    const params = new URLSearchParams({
      access_key: env.SNAPAPI_KEY,
      url,
      format: 'png',
      full_page: '1',
    });

    const snap = await fetch(`https://snapapi.pics/screenshot?${params}`);
    if (!snap.ok) return new Response('Screenshot failed', { status: 502 });

    return new Response(snap.body, {
      headers: {
        'Content-Type': 'image/png',
        'Cache-Control': 'public, max-age=3600',
      },
    });
  },
} satisfies ExportedHandler;

Error Handling Patterns in TypeScript

TypeScript's type system enables structured error handling for SnapAPI calls. Define a result type that wraps either the success value or an error description, and use it to propagate API errors without throwing exceptions across async boundaries. This pattern works well in React Server Components, API routes, and background jobs where uncaught promise rejections cause different behavior depending on the runtime environment:

type Result =
  | { ok: true; data: T }
  | { ok: false; status: number; message: string };

async function safeScreenshot(url: string): Promise> {
  try {
    const data = await client.screenshot({ url, fullPage: true });
    return { ok: true, data };
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    const status = msg.includes('429') ? 429 : msg.includes('5') ? 502 : 500;
    return { ok: false, status, message: msg };
  }
}

const result = await safeScreenshot('https://example.com');
if (result.ok) {
  console.log('Got', result.data.byteLength, 'bytes');
} else {
  console.error('Error', result.status, result.message);
}

Using the Official SnapAPI TypeScript SDK

The official SnapAPI JavaScript/TypeScript SDK on npm (npm install snapapi-js) provides full TypeScript types, a pre-built client class, and built-in retry logic for rate limit responses. Import the client with import { SnapAPI } from 'snapapi-js', initialize it with your API key, and call methods like client.screenshot({ url, format: 'png' }) with full IntelliSense and parameter validation. The SDK handles the parameter serialization, HTTP client configuration, binary response handling, and error class definitions internally, reducing integration code to a minimum. The same SDK supports both Node.js and browser environments (where CORS is configured), making it suitable for isomorphic TypeScript applications where screenshot generation may happen on either the client or server side.

Bun Integration for TypeScript Screenshot Scripts

Bun is a fast JavaScript runtime that runs TypeScript natively without a compilation step, making it ideal for screenshot automation scripts. The native Bun.fetch API is compatible with the Web Fetch API used in the examples above, and Bun.write provides a fast way to save screenshot bytes to disk. Running a TypeScript screenshot script with Bun requires no tsconfig, no tsc compilation step, and no Node.js — just bun run screenshot.ts. For automation scripts that generate dozens or hundreds of screenshots in a batch, Bun's significantly faster startup time compared to Node.js reduces the overhead per-invocation, and its native TypeScript support eliminates the ts-node or tsx dependency from the script runner.

Zod Schema Validation for SnapAPI Responses

TypeScript applications that use SnapAPI's extract endpoint benefit from runtime schema validation using Zod, ensuring that the extracted data matches the expected shape before it is stored or processed. Define a Zod schema that mirrors the extraction schema, parse the API response through it, and handle validation errors explicitly. This pattern prevents silent data quality issues where the extraction model returns a different type than expected — a string instead of a number, a null instead of a boolean — from propagating into your database or downstream processing logic without detection:

import { z } from 'zod';

const ProductSchema = z.object({
  name: z.string(),
  price: z.number(),
  currency: z.string().default('USD'),
  in_stock: z.boolean(),
  description: z.string().optional(),
});
type Product = z.infer;

async function extractProduct(url: string): Promise {
  const raw = await client.extract(url, {
    name: 'string', price: 'number',
    currency: 'string', in_stock: 'boolean', description: 'string'
  });
  return ProductSchema.parse(raw); // throws ZodError on invalid data
}

Testing TypeScript Code That Calls SnapAPI

Vitest and Jest both support mocking the global fetch function for testing TypeScript code that makes HTTP requests. Mock the SnapAPI responses in unit tests to test your business logic independently of the real API — covering success paths, error handling, retry logic, and cache behavior without making actual HTTP calls or consuming your monthly quota. For integration tests that verify real API responses, use environment variables to gate tests behind a real API key check, and run them only in CI when the key is available as a secret. The combination of unit tests with mocked fetch and integration tests with the real API gives you confidence that both your application logic and the API integration work correctly, with the unit tests running fast in development and the integration tests verifying end-to-end correctness in CI.

TypeScript Screenshot Scripts in CI/CD Pipelines

TypeScript screenshot automation runs naturally in CI/CD pipelines as a post-deploy verification step. After deploying to a staging or production environment, run a TypeScript script that captures screenshots of key pages and compares them to stored baselines — or simply saves them as deployment artifacts for manual review. Because the TypeScript script uses SnapAPI rather than a local headless browser, the CI runner needs no special configuration: no browser installation, no system-level dependencies, no custom Docker image with Chrome. The script runs in any Node.js 18+ or Bun environment, which is available by default on GitHub Actions, CircleCI, and most other CI platforms. The total setup time for a TypeScript visual verification step using SnapAPI is under 10 minutes: install the SDK with npm or yarn, add the SNAPAPI_KEY secret to your CI environment, and write the screenshot-and-compare script. Contrast this with the hours required to configure a Playwright browser installation in CI and manage browser version pinning across developer machines and CI environments.