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.