SnapAPI Documentation

SnapAPI is a developer-first API for capturing screenshots, generating PDFs, scraping web data, extracting content for LLMs, and recording videos. Built on Playwright with residential proxies, ad blocking, and cookie banner dismissal out of the box.

Get started in 30 seconds Sign up at snapapi.pics/dashboard to get your API key. The free plan includes 200 requests per month -- no credit card required.

What can you build?

  • Screenshot capture -- URL, HTML, or Markdown to PNG, JPEG, WebP, or AVIF
  • PDF generation -- URL or HTML to PDF with full page-size and margin control
  • Web scraping -- Extract text, HTML, or links from any page with proxy support
  • Content extraction -- Clean markdown for LLM pipelines, article parsing, metadata
  • Video recording -- Record MP4, WebM, or GIF with auto-scroll
  • AI analysis -- Feed page content to OpenAI or Anthropic with your own key (BYOK)
  • Batch processing -- Screenshot up to 100 URLs in a single request

Authentication

All API requests require authentication. SnapAPI supports three methods:

X-Api-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

2. Authorization header

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

3. Query parameter

https://api.snapapi.pics/v1/screenshot?access_key=sk_live_xxx&url=https://example.com
Keep your API key secret Never expose your API key in client-side code, public repositories, or logs. Use environment variables and server-side calls.

API keys are prefixed with sk_live_ and can be created, rotated, and revoked from the Dashboard. Each account supports up to 10 API keys with independent permissions.

Quickstart

Capture your first screenshot in under a minute.

curl -X POST https://api.snapapi.pics/v1/screenshot \
  -H "X-Api-Key: sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com", "format": "png"}' \
  -o screenshot.png
const response = await fetch('https://api.snapapi.pics/v1/screenshot', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'sk_live_YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://example.com',
    format: 'png',
  }),
});

const buffer = await response.arrayBuffer();
// Save or process the screenshot
import httpx

response = httpx.post(
    "https://api.snapapi.pics/v1/screenshot",
    headers={"X-Api-Key": "sk_live_YOUR_API_KEY"},
    json={"url": "https://example.com", "format": "png"},
)

with open("screenshot.png", "wb") as f:
    f.write(response.content)
package main

import (
    "bytes"
    "encoding/json"
    "io"
    "net/http"
    "os"
)

func main() {
    body, _ := json.Marshal(map[string]any{
        "url":    "https://example.com",
        "format": "png",
    })
    req, _ := http.NewRequest("POST",
        "https://api.snapapi.pics/v1/screenshot",
        bytes.NewReader(body))
    req.Header.Set("X-Api-Key", "sk_live_YOUR_API_KEY")
    req.Header.Set("Content-Type", "application/json")

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    file, _ := os.Create("screenshot.png")
    io.Copy(file, resp.Body)
}
<?php
$ch = curl_init('https://api.snapapi.pics/v1/screenshot');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'X-Api-Key: sk_live_YOUR_API_KEY',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'url' => 'https://example.com',
        'format' => 'png',
    ]),
]);
$image = curl_exec($ch);
file_put_contents('screenshot.png', $image);
import Foundation

let url = URL(string: "https://api.snapapi.pics/v1/screenshot")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("sk_live_YOUR_API_KEY", forHTTPHeaderField: "X-Api-Key")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")

let body: [String: Any] = ["url": "https://example.com", "format": "png"]
request.httpBody = try JSONSerialization.data(withJSONObject: body)

let (data, _) = try await URLSession.shared.data(for: request)
try data.write(to: URL(fileURLWithPath: "screenshot.png"))
val client = HttpClient(CIO) {
    install(ContentNegotiation) { json() }
}

val response = client.post("https://api.snapapi.pics/v1/screenshot") {
    header("X-Api-Key", "sk_live_YOUR_API_KEY")
    contentType(ContentType.Application.Json)
    setBody(mapOf("url" to "https://example.com", "format" to "png"))
}

File("screenshot.png").writeBytes(response.readBytes())

Base URL

All API endpoints are served from a single base URL:

https://api.snapapi.pics/v1

All requests must use HTTPS. HTTP requests will be rejected.

Discover API capabilities Call GET /v1/capabilities to see all available features, supported formats, and plan-specific limits for your account.

Screenshot

POST /v1/screenshot

Capture a screenshot of a web page, raw HTML, or Markdown. Returns the image as binary data by default, or as base64/JSON with metadata.

Content source (one required)

ParameterTypeDefaultDescription
url*stringTarget URL to capture. Must include protocol (https://).
htmlstringRaw HTML to render and capture. Max 5 MB.
markdownstringMarkdown to render with styled template. Max 1 MB.
One of url, html, or markdown is required. If you provide markdown, it is automatically converted to styled HTML before rendering.

Output format

ParameterTypeDefaultDescription
formatstringpngOutput format: png, jpeg, webp, avif, or pdf.
qualityinteger80Image quality 1-100. Applies to JPEG, WebP, and AVIF only.
responseTypestringbinarybinary returns raw image data. base64 returns base64-encoded string. json returns JSON with metadata and base64 data.
includeMetadatabooleanfalseInclude page title, fonts, colors, and links in JSON response.

Viewport

ParameterTypeDefaultDescription
devicestringDevice preset (e.g. iphone-15-pro, macbook-pro-16). Overrides width/height. See device list.
widthinteger1280Viewport width in pixels. Range: 100-3840.
heightinteger800Viewport height in pixels. Range: 100-2160.
deviceScaleFactornumber1Device pixel ratio (1-3). Use 2 for retina-quality images.
retinabooleanfalseShortcut for deviceScaleFactor: 2.
isMobilebooleanfalseEmulate mobile browser viewport.
hasTouchbooleanfalseEmulate touch support.
isLandscapebooleanfalseLandscape orientation.

Full page & element selection

ParameterTypeDefaultDescription
fullPagebooleanfalseCapture the full scrollable page height.
fullPageScrollDelayinteger400Delay (ms) between scroll steps for lazy-loaded content.
fullPageMaxHeightintegerMaximum height for full-page captures (100-50000 px).
scrollToBottombooleanfalseScroll to bottom before capturing (triggers lazy loads).
selectorstringCSS selector -- capture only that element.
clipX, clipY, clipWidth, clipHeightintegerClip a rectangular region from the viewport.

Timing

ParameterTypeDefaultDescription
delayinteger0Wait (ms) after page load before capturing. Range: 0-30000.
timeoutinteger30000Navigation timeout (ms). Range: 1000-60000.
waitUntilstringloadload, domcontentloaded, or networkidle. Use networkidle for SPAs.
waitForSelectorstringWait for this CSS selector to appear before capturing.
waitForSelectorTimeoutinteger10000Timeout for waitForSelector (ms).

Customization

ParameterTypeDefaultDescription
darkModebooleanfalseEnable prefers-color-scheme: dark.
reducedMotionbooleanfalseEnable prefers-reduced-motion.
cssstringCustom CSS to inject before capture. Max 100 KB. Starter+
javascriptstringCustom JavaScript to execute before capture. Max 100 KB. Pro+
hideSelectorsstring[]Array of CSS selectors to hide before capture. Max 50.
clickSelectorstringClick this element before capturing.
clickDelayintegerWait (ms) after clicking before capture.

Blocking

ParameterTypeDefaultDescription
blockAdsbooleanfalseBlock advertisements. Starter+
blockTrackersbooleanfalseBlock tracking scripts. Pro+
blockCookieBannersbooleanfalseAuto-dismiss cookie consent banners (supports 20+ CMPs).
blockChatWidgetsbooleanfalseHide chat/support widgets.
blockResourcesstring[]Resource types to block: image, font, stylesheet, script, media, xhr, fetch, websocket.

Request options

ParameterTypeDefaultDescription
userAgentstringOverride the browser User-Agent string.
extraHeadersobjectAdditional HTTP headers to send with the request.
cookiesobject[]Cookies to set before navigation. Each: {name, value, domain?, path?}.
httpAuthobjectHTTP Basic Auth: {username, password}.
proxyobjectCustom proxy: {server, username?, password?}.
premiumProxybooleanRoute through managed residential proxy for geo-restricted or bot-protected sites.
geolocationobjectEmulate GPS: {latitude, longitude, accuracy?}.
timezonestringTimezone override (e.g. America/New_York).
localestringBrowser locale (e.g. en-US).

Content validation

ParameterTypeDefaultDescription
failOnHttpErrorbooleanfalseReturn an error if the page responds with an HTTP error status.
failIfContentMissingstring[]Fail if any of these text strings are not found on the page.
failIfContentContainsstring[]Fail if any of these text strings are found on the page.

Storage & caching

ParameterTypeDefaultDescription
cachebooleanfalseCache the result in Redis. Pro+
cacheTtlinteger86400Cache TTL in seconds (60 to 2,592,000 = 30 days).
storageobject{enabled: true, destination: "snapapi" | "user_s3"}. Store in SnapAPI Vault or your own S3 bucket.
s3objectDirect upload to your S3 bucket inline: {bucket, key?, region?, endpoint?, access_key_id, secret_access_key, acl?}.

Async & webhooks

ParameterTypeDefaultDescription
asyncbooleanfalseReturn a job ID immediately and process in the background.
webhookUrlstringURL to POST the result to when processing completes.
webhookHeadersobjectCustom headers to include in webhook delivery.

Thumbnail

ParameterTypeDefaultDescription
thumbnailobject{enabled: true, width?: 50-800, height?: 50-600, fit?: "cover"|"contain"|"fill"}. Generate a thumbnail alongside the screenshot.

Response

When responseType is "binary" (default), the response is the raw image with the appropriate Content-Type header.

When responseType is "json":

200 Success
{
  "success": true,
  "data": "iVBORw0KGgo...",         // base64 image data
  "format": "png",
  "width": 1280,
  "height": 800,
  "fileSize": 245832,           // bytes
  "took": 1523,                // processing time in ms
  "cached": false
}
400 Validation error
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed on 1 field. See details for per-field errors.",
    "details": [
      { "field": "url", "message": "'url' must be a valid URL (e.g. https://example.com)" }
    ],
    "requestId": "req_abc123",
    "docs": "https://api.snapapi.pics/v1/docs"
  }
}

Full example with options

curl -X POST https://api.snapapi.pics/v1/screenshot \
  -H "X-Api-Key: sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://github.com",
    "format": "webp",
    "quality": 90,
    "width": 1440,
    "height": 900,
    "fullPage": true,
    "darkMode": true,
    "blockAds": true,
    "blockCookieBanners": true,
    "delay": 1000,
    "responseType": "json"
  }'
const response = await fetch('https://api.snapapi.pics/v1/screenshot', {
  method: 'POST',
  headers: {
    'X-Api-Key': process.env.SNAPAPI_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://github.com',
    format: 'webp',
    quality: 90,
    width: 1440,
    height: 900,
    fullPage: true,
    darkMode: true,
    blockAds: true,
    blockCookieBanners: true,
    delay: 1000,
    responseType: 'json',
  }),
});

const { data, format, width, height, took } = await response.json();
console.log(`Captured ${width}x${height} in ${took}ms`);
import httpx
import os

response = httpx.post(
    "https://api.snapapi.pics/v1/screenshot",
    headers={"X-Api-Key": os.environ["SNAPAPI_KEY"]},
    json={
        "url": "https://github.com",
        "format": "webp",
        "quality": 90,
        "width": 1440,
        "height": 900,
        "fullPage": True,
        "darkMode": True,
        "blockAds": True,
        "blockCookieBanners": True,
        "delay": 1000,
        "responseType": "json",
    },
)
data = response.json()
print(f"Captured {data['width']}x{data['height']} in {data['took']}ms")

Screenshot (GET)

GET /v1/screenshot

Convenience GET endpoint for simple screenshot requests. Pass options as query parameters. Useful for embedding screenshots directly in <img> tags.

# Direct browser link:
https://api.snapapi.pics/v1/screenshot?access_key=sk_live_xxx&url=https://example.com&format=png&width=1280

# Embed in HTML:
<img src="https://api.snapapi.pics/v1/screenshot?access_key=sk_live_xxx&url=https://example.com" />

Supported query parameters: url, format, quality, width, height, full_page, delay, dark_mode, block_ads, cache, response_type.

PDF Generation

POST /v1/pdf

Generate a PDF from a URL or HTML. This is a shortcut for POST /v1/screenshot with format: "pdf". You can also pass format: "pdf" directly to the screenshot endpoint.

PDF-specific options

ParameterTypeDefaultDescription
url*stringURL to convert to PDF.
htmlstringRaw HTML to convert to PDF.
pdfOptions.pageSizestringa4, a3, a5, letter, legal, tabloid, or custom.
pdfOptions.landscapebooleanfalseLandscape page orientation.
pdfOptions.printBackgroundbooleanfalseInclude background colors and images.
pdfOptions.scalenumber1Scale factor (0.1 to 2).
pdfOptions.marginTop/Right/Bottom/LeftstringMargins (e.g. "1cm", "0.5in").
pdfOptions.headerTemplatestringHTML template for the header. Use classes: date, title, url, pageNumber, totalPages.
pdfOptions.footerTemplatestringHTML template for the footer.
pdfOptions.displayHeaderFooterbooleanfalseShow header and footer.
pdfOptions.pageRangesstringPaper ranges to print (e.g. "1-5", "1,3,5").

Example

curl -X POST https://api.snapapi.pics/v1/pdf \
  -H "X-Api-Key: sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/invoice",
    "pdfOptions": {
      "pageSize": "a4",
      "printBackground": true,
      "marginTop": "1cm",
      "marginBottom": "1cm"
    }
  }' \
  -o invoice.pdf
const response = await fetch('https://api.snapapi.pics/v1/pdf', {
  method: 'POST',
  headers: {
    'X-Api-Key': process.env.SNAPAPI_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://example.com/invoice',
    pdfOptions: {
      pageSize: 'a4',
      printBackground: true,
      marginTop: '1cm',
      marginBottom: '1cm',
    },
  }),
});

const pdf = await response.arrayBuffer();
import httpx

response = httpx.post(
    "https://api.snapapi.pics/v1/pdf",
    headers={"X-Api-Key": "sk_live_YOUR_API_KEY"},
    json={
        "url": "https://example.com/invoice",
        "pdfOptions": {
            "pageSize": "a4",
            "printBackground": True,
            "marginTop": "1cm",
            "marginBottom": "1cm",
        },
    },
)

with open("invoice.pdf", "wb") as f:
    f.write(response.content)

Batch Screenshots

POST /v1/screenshot/batch

Queue up to 100 URLs for asynchronous screenshot processing. Returns a job ID to poll.

ParameterTypeDefaultDescription
urls*string[]Array of URLs to screenshot. 1-100 items.
formatstringpngOutput format for all screenshots.
widthinteger1280Viewport width for all screenshots.
heightinteger800Viewport height for all screenshots.
fullPagebooleanfalseFull page capture for all.
darkModebooleanfalseDark mode for all.
blockAdsbooleanfalseBlock ads for all.
webhookUrlstringWebhook called when the batch completes.
202 Accepted
{
  "success": true,
  "jobId": "batch_abc123",
  "status": "pending",
  "total": 5,
  "message": "Batch job created. Poll /v1/screenshot/batch/batch_abc123 for status."
}

Poll the status with GET /v1/screenshot/batch/:jobId.

Web Scraping

POST /v1/scrape

Scrape content from any web page. Returns clean text (as markdown), raw HTML, or extracted links. Supports multi-page scraping and residential proxies.

ParameterTypeDefaultDescription
url*stringURL to scrape.
typestringtexttext (converted to markdown), html (raw HTML), or links (all anchor tags).
pagesinteger1Number of pages to scrape (1-10). Follows pagination.
waitMsinteger0Wait (ms) after page load before scraping. Useful for JS-rendered content.
proxystringCustom proxy URL (e.g. http://user:pass@host:port).
premiumProxybooleanUse managed residential proxy.
blockResourcesbooleanfalseBlock images, media, and fonts for faster scraping.
localestringBrowser locale (e.g. en-US).

Also available as GET /v1/scrape with query parameters: url, type, pages, wait_ms, block_resources, locale.

200 Success
{
  "success": true,
  "results": [
    {
      "page": 1,
      "url": "https://example.com",
      "data": "# Example Domain\n\nThis domain is for use in illustrative examples..."
    }
  ]
}
curl -X POST https://api.snapapi.pics/v1/scrape \
  -H "X-Api-Key: sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://news.ycombinator.com", "type": "links"}'
const response = await fetch('https://api.snapapi.pics/v1/scrape', {
  method: 'POST',
  headers: {
    'X-Api-Key': process.env.SNAPAPI_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://news.ycombinator.com',
    type: 'links',
  }),
});

const { results } = await response.json();
const links = JSON.parse(results[0].data);
import httpx

response = httpx.post(
    "https://api.snapapi.pics/v1/scrape",
    headers={"X-Api-Key": "sk_live_YOUR_API_KEY"},
    json={"url": "https://news.ycombinator.com", "type": "links"},
)

data = response.json()
links = data["results"][0]["data"]

Content Extraction

POST /v1/extract

Extract clean, structured content from any web page. Optimized for LLM pipelines -- feed the output directly to ChatGPT, Claude, or your own models.

ParameterTypeDefaultDescription
url*stringURL to extract content from.
typestringmarkdown markdown -- clean markdown (best for LLMs)
text -- plain text
html -- raw HTML
article -- article with title, author, excerpt via Readability
links -- all links on the page
images -- all images with src, alt, dimensions
metadata -- page metadata (OG tags, title, description)
structured -- combined: content + metadata + word count
selectorstringCSS selector to extract from a specific element.
waitForstringWait for this selector before extracting.
timeoutinteger30000Navigation timeout (ms).
darkModebooleanfalseEnable dark mode.
blockAdsbooleanfalseBlock ads.
blockCookieBannersbooleanfalseDismiss cookie banners.
includeImagesbooleantrueInclude images in extracted content.
maxLengthintegerTruncate content to this many characters (100-500000).
cleanOutputbooleantrueRemove empty links, redundant whitespace.
fieldsobjectStructured field extraction: {"price": "product price", "title": "main heading"}.
200 Success (type=article)
{
  "success": true,
  "type": "article",
  "url": "https://example.com/blog/post",
  "data": {
    "title": "How to Build a REST API",
    "byline": "Jane Doe",
    "siteName": "Example Blog",
    "excerpt": "A comprehensive guide to building REST APIs...",
    "length": 4523,
    "markdown": "# How to Build a REST API\n\nA comprehensive guide..."
  },
  "responseTime": 2341
}
LLM pipeline tip Use type: "markdown" with maxLength: 50000 to get clean content that fits within most LLM context windows. Combine with blockAds: true and blockCookieBanners: true for noise-free extraction.

Video Recording

POST /v1/video

Record a video of a web page. Supports auto-scrolling, multiple output formats, and custom viewport sizes. Max 3 concurrent video jobs per server.

ParameterTypeDefaultDescription
url*stringURL to record.
formatstringmp4mp4, webm, or gif.
widthinteger1280Viewport width (320-1920).
heightinteger720Viewport height (240-1080).
durationinteger5Recording duration in seconds (1-30).
fpsinteger25Frames per second (10-30).
scrollingbooleanfalseEnable auto-scroll recording.
scrollSpeedinteger100Scroll speed (50-500 px/step).
scrollDelayinteger1000Delay between scrolls (ms).
scrollDurationinteger1500Duration of each scroll animation (ms).
scrollByinteger800Pixels per scroll step (100-2000).
scrollEasingstringease_in_outlinear, ease_in, ease_out, ease_in_out, ease_in_out_quint.
scrollBackbooleantrueScroll back to top at end.
scrollCompletebooleantrueStop recording when scroll completes.
darkModebooleanfalseEnable dark mode.
blockAdsbooleanfalseBlock ads during recording.
blockCookieBannersbooleanfalseDismiss cookie banners.
delayinteger0Wait (ms) before starting recording.
responseTypestringbinarybinary, base64, or json.

Example

curl -X POST https://api.snapapi.pics/v1/video \
  -H "X-Api-Key: sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "format": "mp4",
    "duration": 10,
    "scrolling": true,
    "scrollEasing": "ease_in_out"
  }' \
  -o recording.mp4
const response = await fetch('https://api.snapapi.pics/v1/video', {
  method: 'POST',
  headers: {
    'X-Api-Key': process.env.SNAPAPI_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://example.com',
    format: 'mp4',
    duration: 10,
    scrolling: true,
    scrollEasing: 'ease_in_out',
  }),
});

const fs = await import('fs');
fs.writeFileSync('recording.mp4', Buffer.from(await response.arrayBuffer()));

AI Analysis (BYOK)

POST /v1/analyze

Analyze a web page using AI. SnapAPI loads the page, extracts content, and sends it to OpenAI or Anthropic with your prompt. Bring Your Own Key (BYOK) -- you provide your own LLM API key.

ParameterTypeDefaultDescription
url*stringURL to analyze.
prompt*stringYour analysis prompt (max 5000 chars).
providerstringopenaiopenai or anthropic.
apiKey*stringYour OpenAI or Anthropic API key.
modelstringModel override (e.g. gpt-4o, claude-3-opus-20240229).
jsonSchemaobjectJSON schema for structured output (OpenAI only).
includeScreenshotbooleanfalseInclude a screenshot for vision models.
includeMetadatabooleantrueInclude page metadata in the prompt context.
maxContentLengthinteger50000Max content characters sent to the LLM.

Job Status

GET /v1/jobs/:jobId

Check the status of an async screenshot or batch job. Add ?includeData=true to include the base64 image data in the response once complete.

200 Success
{
  "success": true,
  "jobId": "abc123",
  "status": "completed",    // pending | processing | completed | failed
  "format": "png",
  "width": 1280,
  "height": 800,
  "createdAt": "2026-03-23T10:00:00Z",
  "completedAt": "2026-03-23T10:00:03Z"
}

Device Presets

GET /v1/devices

Returns all supported device presets grouped by category. No authentication required.

Available presets

  • Desktop: desktop-1080p, desktop-1440p, desktop-4k, macbook-pro-13, macbook-pro-16, imac-24
  • Mobile: iphone-12 through iphone-15-pro-max, iphone-se, pixel-7, pixel-8, pixel-8-pro, samsung-galaxy-s23, samsung-galaxy-s24
  • Tablet: ipad, ipad-mini, ipad-air, ipad-pro-11, ipad-pro-12.9, samsung-galaxy-tab-s9

Scheduled Screenshots

POST /v1/scheduled

Create recurring screenshot jobs that run on a schedule. Manage schedules via the API or Dashboard.

Management endpoints

MethodEndpointDescription
GET/v1/scheduledList all scheduled jobs.
POST/v1/scheduledCreate a scheduled screenshot job.
GET/v1/scheduled/:idGet schedule details.
PATCH/v1/scheduled/:idUpdate a schedule.
DELETE/v1/scheduled/:idDelete a schedule.
POST/v1/scheduled/:id/runTrigger an immediate run of a scheduled job.
GET/v1/scheduled/presetsList available schedule presets (e.g. hourly, daily, weekly).

Webhooks

Register webhook endpoints to receive notifications when async jobs complete, batches finish, or quota thresholds are hit. Manage webhooks via the API or Dashboard.

Supported events

  • screenshot.completed -- async screenshot finished successfully
  • screenshot.failed -- async screenshot failed
  • batch.completed -- batch job finished
  • quota.warning -- 80% of monthly quota used
  • quota.exceeded -- monthly quota exceeded

To see all supported events programmatically, call GET /v1/webhooks/events.

Webhook management

MethodEndpointDescription
GET/v1/webhooksList all webhooks.
POST/v1/webhooksCreate a webhook. Body: {url, events[], secret?}. Max 5 per account.
PATCH/v1/webhooks/:idUpdate a webhook.
DELETE/v1/webhooks/:idDelete a webhook.
POST/v1/webhooks/:id/testSend a test payload.
GET/v1/webhooks/:id/deliveriesView webhook delivery history.

Webhook payload

{
  "event": "screenshot.completed",
  "jobId": "abc123",
  "success": true,
  "data": "iVBORw0KGgo...",
  "format": "png",
  "width": 1280,
  "height": 800,
  "fileSize": 245832
}

Verifying signatures

Every webhook delivery includes an X-SnapAPI-Signature header with an HMAC-SHA256 signature of the payload body, signed with your webhook secret.

const crypto = require('crypto');

function verifyWebhook(body, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Webhook deliveries are retried up to 5 times with exponential backoff: immediate, 1 min, 5 min, 15 min, 1 hour.

Storage / Vault

Store screenshots, PDFs, and videos in SnapAPI's managed cloud storage (Vault) or upload directly to your own S3-compatible bucket.

Using SnapAPI Vault

Enable Vault storage by passing "storage": {"enabled": true, "destination": "snapapi"} in your screenshot request. Files are stored securely and accessible via signed URLs.

Using your own S3 bucket

Configure your S3 credentials in the Dashboard under Storage, or pass them inline with each request:

{
  "url": "https://example.com",
  "s3": {
    "bucket": "my-screenshots",
    "region": "us-east-1",
    "access_key_id": "AKIAIOSFODNN7EXAMPLE",
    "secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLE",
    "acl": "private"
  }
}

Storage management endpoints

MethodEndpointDescription
GET/storage/overviewStorage usage summary (requires Bearer token auth).
GET/storage/filesList stored files.
POST/storage/user-s3Configure your S3 bucket settings.
POST/storage/vault-preferenceEnable/disable Vault storage.

Caching

Enable caching to avoid re-capturing identical screenshots. Cached results are stored in Redis and returned instantly without consuming quota. Pro+

How it works

  1. Pass "cache": true in your request.
  2. SnapAPI generates a cache key from the relevant parameters: URL, format, quality, width, height, deviceScaleFactor, fullPage, selector, darkMode, and custom CSS.
  3. If a cached result exists and is within the TTL, it is returned immediately with X-Screenshot-Cached: true header.
  4. Cached responses do not count toward your monthly quota.

Cache TTL

Set "cacheTtl" in seconds. Default: 86400 (24 hours). Range: 60 seconds to 2,592,000 seconds (30 days).

Cache busting To force a fresh capture, simply omit the cache parameter or set it to false. Changing any parameter that is part of the cache key (e.g. width, format) will also produce a new capture.

Async Processing

For long-running captures or when you do not want to hold an HTTP connection open, use async mode. The API returns a job ID immediately and processes the request in the background.

How to use async

  1. Add "async": true to any screenshot request.
  2. The API returns 202 Accepted with a jobId and statusUrl.
  3. Poll GET /v1/jobs/:jobId until status is "completed" or "failed".
  4. Alternatively, set a webhookUrl and receive the result via HTTP POST when done.
// Step 1: Start async job
const { jobId, statusUrl } = await fetch('https://api.snapapi.pics/v1/screenshot', {
  method: 'POST',
  headers: { 'X-Api-Key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({ url: 'https://example.com', async: true }),
}).then(r => r.json());

// Step 2: Poll for result
let result;
do {
  await new Promise(r => setTimeout(r, 2000));
  result = await fetch(statusUrl, {
    headers: { 'X-Api-Key': apiKey },
  }).then(r => r.json());
} while (result.status === 'pending' || result.status === 'processing');

Error Codes

All errors follow a consistent format with a machine-readable code, human-readable message, and a requestId for debugging.

CodeStatusDescription
VALIDATION_ERROR400One or more request parameters are invalid. Check the details array for per-field errors.
BAD_REQUEST400Generic bad request (e.g. missing required fields).
INVALID_URL400The URL is invalid, unreachable, or points to a private/internal IP (SSRF protection).
MISSING_API_KEY401No API key provided in the request.
UNAUTHORIZED401API key is invalid, expired, or revoked.
FORBIDDEN403Your plan does not include this feature. Upgrade at snapapi.pics/pricing.
NOT_FOUND404Resource or endpoint not found.
CONFLICT409Conflict (e.g. email already registered).
RATE_LIMITED429Too many requests. See rate limit headers.
QUOTA_EXCEEDED429Monthly quota exceeded. Upgrade your plan or wait for reset.
INTERNAL_ERROR500Something went wrong on our end. The requestId can be used for support requests.

Error response format

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed on 2 fields. See details for per-field errors.",
    "details": [
      { "field": "url", "message": "'url' must be a valid URL", "code": "invalid_string" },
      { "field": "format", "message": "'format' must be one of: 'png', 'jpeg', 'webp', 'avif', 'pdf'" }
    ],
    "requestId": "req_abc123",
    "docs": "https://api.snapapi.pics/v1/docs"
  }
}
Retry strategy For 429 errors, implement exponential backoff. For 500 errors, retry up to 3 times with 1s, 2s, 4s delays. Never retry 400 or 401 errors -- fix the request first.

Rate Limits

SnapAPI enforces monthly request quotas per plan. Rate limit information is included in every response via headers.

Free

200
requests / month

Starter

5,000
requests / month

Pro

50,000
requests / month

Business

500,000
requests / month

Rate limit headers

HeaderDescription
X-RateLimit-LimitYour monthly quota.
X-RateLimit-RemainingRemaining requests this month.
X-RateLimit-ResetISO 8601 timestamp when quota resets.

When you exceed your quota, the API returns a 429 status with the QUOTA_EXCEEDED error code. Cached responses (cache: true) do not count against your quota.

Additionally, nginx enforces a global rate limit of 50 requests per second per IP with a burst capacity of 100 to protect against abuse.

SDKs

Official client libraries for 8 languages. All SDKs include type definitions, retry logic with exponential backoff, and consistent error handling.

JavaScript / TypeScript

npm i github:Sleywill/snapapi-js GitHub →

Python

pip install git+https://github.com/Sleywill/snapapi-python GitHub →

Go

go get github.com/Sleywill/snapapi-go GitHub →

PHP

composer require sleywill/snapapi-php GitHub →

Swift

.package(url: "https://github.com/Sleywill/snapapi-swift", from: "2.0.0") GitHub →

Kotlin

implementation("dev.snapapi:sdk:1.0.0") GitHub →

Ruby

gem install snapapi GitHub →

Java

implementation 'pics.snapapi:sdk:2.0.0' GitHub →

SDK quickstart (JavaScript)

import { SnapAPI } from 'snapapi';

const client = new SnapAPI({ apiKey: process.env.SNAPAPI_KEY });

// Screenshot
const screenshot = await client.screenshot({
  url: 'https://example.com',
  format: 'png',
  fullPage: true,
});

// Scrape
const content = await client.scrape({
  url: 'https://example.com',
  type: 'text',
});

// Extract for LLM
const markdown = await client.extract({
  url: 'https://example.com/blog/post',
  type: 'article',
});

SDK quickstart (Python)

from snapapi import SnapAPI

client = SnapAPI(api_key="sk_live_YOUR_API_KEY")

# Screenshot
screenshot = client.screenshot(url="https://example.com", format="png")

# Extract for LLM pipeline
article = client.extract(
    url="https://example.com/blog/post",
    type="article",
    block_ads=True,
)

SDK quickstart (Ruby)

require 'snapapi'

client = SnapAPI::Client.new(api_key: ENV['SNAPAPI_KEY'])

# Screenshot
screenshot = client.screenshot(url: 'https://example.com', format: 'png')

# Extract for LLM pipeline
article = client.extract(url: 'https://example.com/blog/post', type: 'article')

SDK quickstart (Java)

import pics.snapapi.SnapAPIClient;
import pics.snapapi.ScreenshotRequest;

SnapAPIClient client = new SnapAPIClient("sk_live_YOUR_API_KEY");

// Screenshot
byte[] screenshot = client.screenshot(
    ScreenshotRequest.builder()
        .url("https://example.com")
        .format("png")
        .fullPage(true)
        .build()
);

// Extract for LLM pipeline
String article = client.extract(
    ExtractRequest.builder()
        .url("https://example.com/blog/post")
        .type("article")
        .build()
);

Changelog

v2.0.0 -- March 2026

  • Added AVIF format support for screenshots
  • Added device presets (25+ devices)
  • Added batch screenshot endpoint (/v1/screenshot/batch)
  • Added async processing with webhook delivery
  • Added inline S3 direct upload (s3 parameter)
  • Added AI analysis endpoint (/v1/analyze) with BYOK
  • Added content validation (failIfContentMissing, failIfContentContains)
  • Added thumbnail generation
  • Added extract types: article, images, metadata, structured
  • Added scroll video recording with easing functions
  • Improved cookie banner dismissal (20+ CMP support)
  • Improved error messages with per-field validation details
  • All 8 SDKs updated to v2.0.0

v1.0.0 -- January 2026

  • Initial release
  • Screenshot, scrape, extract, video, PDF endpoints
  • Free, Starter, and Pro plans
  • JavaScript and Python SDKs