Documentation
Introduction
SnapAPI turns any URL into an image. Send an HTTP request with a URL and capture options, and get back a PNG, JPEG, WebP, or PDF file — or a link to one. A second endpoint extracts page content as clean Markdown and structured article data for RAG, indexing, and AI agents. It works with any language, framework, or platform that can make HTTP requests, and with AI assistants through the Model Context Protocol (MCP).
Every screenshot consumes credits based on your plan. You can track usage and balances in the dashboard.
Quickstart
- 1. Get an API key. Sign in and create a key on the API Keys page in the dashboard.
- 2. Send a request. POST the target URL to
/api/v1/screenshot. - 3. Use the result. Receive JSON with a temporary image link, or raw image bytes.
curl -X POST https://snapapi.top/api/v1/screenshot \
-H "Authorization: Bearer snap_your_api_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'Authentication
All requests must include your API key in the Authorization header:
Authorization: Bearer snap_your_api_key
Create and revoke keys at any time from the API Keys page in the dashboard. Requests without a valid key return 401 Unauthorized.
Screenshot API
POST /api/v1/screenshot — send options as JSON with Content-Type: application/json.
{
"url": "https://example.com",
"fullPage": true,
"format": "png",
"width": 1280,
"wait": "networkidle"
}Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | required | The URL of the page to capture. Must be a valid http(s) URL. |
width | integer | 1280 | Viewport width in pixels (320–3840). |
height | integer | 800 | Viewport height in pixels (240–2160). |
deviceScaleFactor | integer | 1 | Device scale factor, 1–3. Use 2 or 3 for crisp retina-quality images. |
fullPage | boolean | false | Capture the entire scrollable page instead of only the visible viewport. The page is automatically scrolled through first so lazy-loaded content below the fold is rendered (capped at ~16,000px scroll height). Pages no taller than the viewport take the fast path and cost no extra scroll time; long infinitely-scrolling pages may spend up to ~8s priming before capture. |
dark | boolean | false | Emulate the page’s dark color scheme. |
format | string | "png" | Output format: png, jpeg, webp, or pdf. |
quality | integer | 85 | Quality from 1 to 100 for JPEG and WebP. Ignored for PNG and PDF. |
wait | string | "domcontentloaded" | When to consider the page ready: domcontentloaded, load, or networkidle. |
delay | integer | 0 | Extra wait time after the ready event, in milliseconds (0–10000). |
selector | string | — | CSS selector of a single element to capture, e.g. "#main-card". |
noBanners | boolean | true | Automatically hide cookie banners and consent popups. |
clean | boolean | false | Clean mode: block requests to known ad, analytics, and tracker domains at the network level, hide leftover ad elements in the DOM, and remove cookie banners. Requires the Starter or Pro plan (FREE returns 403). |
response | string | "json" | Response type: json returns a link plus metadata; binary returns raw image bytes. |
Waiting strategies
Use wait to decide when the page is considered ready:
domcontentloaded— the HTML has parsed. Fastest; good for simple pages.load— images and stylesheets have finished loading.networkidle— no network activity for 500ms. Best for JavaScript-heavy pages. Combine withdelayfor animations or late content.
Output formats
png— palette-quantized PNG, visually near-lossless: the server recompresses PNG output with pngquant (quality 65–85), typically shrinking large full-page captures by 60–75%. Pick JPEG or WebP for photo-heavy pages where smallest transfer size matters.jpeg— smaller files; tune withquality.webp— modern format, smaller than PNG or JPEG at the same quality; tune withquality.pdf— paginated documents based on the full page.
Responses
JSON (default)
With response: "json" you receive metadata and a temporary link to the captured image. The link is valid for 15 minutes — download or copy the image if you need it longer.
{
"ok": true,
"storage": "minio",
"url": "https://snapapi.top/api/v1/files/...",
"contentType": "image/png",
"bytes": 184320,
"durationMs": 1200,
"creditsUsed": 1
}| Field | Type | Description |
|---|---|---|
ok | boolean | Always true on success (false on errors). |
storage | string | Where the file was written: "minio" (platform storage) or "s3" (your own AWS S3 / Cloudflare R2 bucket, configured in the dashboard). See the Cloud Storage section. |
url | string | null | Temporary signed link to the captured file (/api/v1/files/…), valid for 15 minutes and usable without an API key. null when storage is "s3" (the file is in your own bucket — read bucket/key instead), or when platform storage is temporarily unavailable (retry with "response": "binary"). |
bucket | string | null | Present only when storage is "s3": the destination bucket name. Together with key, region, endpoint, and provider it locates the object in your own bucket. |
key | string | null | Present only when storage is "s3": the object key written to your bucket, shaped <keyPrefix>/<yyyy>/<mm>/<dd>/<uuid>.<ext> (default prefix "snapapi", customizable in the dashboard; scheduled and batch deliveries add scheduled/ and batch/ sub-prefixes). No userId segment is included. |
region | string | null | Present only when storage is "s3": the bucket region (empty for R2). |
endpoint | string | null | Present only when storage is "s3": the custom S3-compatible endpoint, if configured (for example Cloudflare R2). |
provider | string | null | Present only when storage is "s3": the storage provider code ("aws" or "r2"). |
contentType | string | MIME type of the result: image/png, image/jpeg, image/webp, or application/pdf. |
bytes | integer | File size in bytes. |
durationMs | integer | Server-side capture time in milliseconds. |
creditsUsed | integer | Credits charged for this call. |
Responses only carry standard HTTP headers. Your credit balance, usage history, and rate-limit status are available in the dashboard — or via the account_info MCP tool for agents.
Binary
With response: "binary" the image bytes are returned directly, with the matching Content-Type. Stream the response straight to a file, an <img> proxy, or object storage:
curl -X POST https://snapapi.top/api/v1/screenshot \
-H "Authorization: Bearer snap_your_api_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "response": "binary"}' \
--output screenshot.pngContent Extraction API
POST /api/v1/extract renders a page in a real browser and returns its content as Markdown and structured data: clean article text (title, author, publish date, excerpt, body), SEO/Open Graph metadata, a heading outline, and outbound links. Add "html" to formats to also get the raw rendered HTML. Each call consumes credits, and the feature is available on the Starter and Pro plans.
{
"url": "https://example.com/blog/post",
"formats": ["markdown", "structured"],
"wait": "load",
"maxChars": 100000
}| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | required | The URL of the page to extract. Must be a valid http(s) URL. |
formats | string[] | ["markdown","structured"] | Any of "html" (raw rendered HTML, large), "markdown" (article body as Markdown) and "structured" (metadata, article, outline, links). |
wait | string | "load" | When the page is considered ready: domcontentloaded, load, or networkidle. Use networkidle for JavaScript-heavy SPAs. |
delay | integer | 0 | Extra wait time after the ready event, in milliseconds (0–10000). |
maxChars | integer | 100000 | Maximum characters per text field (1000–2000000); longer fields are truncated and truncated is set to true. |
noBanners | boolean | true | Remove cookie banners and consent popups before extraction. |
clean | boolean | false | Clean Mode — block ad and tracking requests at the network level, speeding up the load and reducing boilerplate noise. |
A successful response looks like this:
{
"ok": true,
"url": "https://example.com/blog/post",
"finalUrl": "https://example.com/blog/post",
"statusCode": 200,
"content": {
"markdown": "# Article title\n\nFirst paragraph ..."
},
"structured": {
"metadata": {
"title": "Article title",
"description": "SEO description",
"ogImage": "https://example.com/cover.png"
},
"article": {
"title": "Article title",
"byline": "Jane Doe",
"publishedTime": "2026-08-01T09:00:00.000Z",
"excerpt": "First paragraph of the article ...",
"siteName": "Example Blog",
"length": 8421,
"text": "Plain-text article body ..."
},
"outline": [{ "level": 1, "text": "Article title", "id": "" }],
"links": [{ "text": "Reference", "href": "https://ref.example" }]
},
"truncated": false,
"bytes": 18432,
"durationMs": 1100,
"creditsUsed": 1
}Response fields:
| Field | Type | Description |
|---|---|---|
ok | boolean | Always true on success (false on errors). |
url | string | The requested URL. |
finalUrl | string | The URL after any redirects. |
statusCode | integer | Target page’s HTTP status code; 0 when the navigation produced no response. |
content | object | Present only when formats includes markdown or html: content.markdown and/or content.html strings. Omitted entirely for structured-only requests. |
structured | object | Present when formats includes structured (the default): metadata, article, outline, links — see the Structured content table below. Omitted for html-only requests. |
truncated | boolean | true if any text field was cut at the maxChars limit. |
bytes | integer | UTF-8 size of the returned content in bytes. |
durationMs | integer | Server-side extraction time in milliseconds. |
creditsUsed | integer | Credits charged for this call. |
Structured content
The structured object is included whenever formats contains structured (the default). Its fields are:
| Field | Type | Description |
|---|---|---|
structured.metadata | object | SEO/Open Graph metadata: url, title, description, keywords, author, ogImage, ogTitle, ogDescription, favicon, lang, charset, viewport. Image/favicon URLs are resolved to absolute. |
structured.article | object | null | Readability article data, or null on non-article pages (login screens, dashboards, …). Fields: title, byline, publishedTime, excerpt, siteName, lang, length (original text length before truncation), text (plain-text body, truncated to maxChars). |
structured.outline | array | Page headings as { level, text, id } objects (h1–h6, up to 100). |
structured.links | array | Outbound links as { text, href } objects, deduplicated and capped. |
For JavaScript-heavy single-page apps, use "wait": "networkidle" plus delay so late content finishes rendering. Text fields longer than maxChars are truncated and truncated is set to true. Non-article pages (login screens, dashboards) return article: null while metadata, outline, and links remain populated.
curl -X POST https://snapapi.top/api/v1/extract \
-H "Authorization: Bearer snap_your_api_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/blog/post"}'Scroll Video API
POST /api/v1/video records a real browser smoothly scrolling the page from top to bottom and returns a playable video (MP4/WebM). Sticky headers, scroll animations, parallax, and lazy-loaded images are captured as they appear — the page is pre-scrolled once before recording so lazy content is loaded. Scroll video is available exclusively on the Pro plan. Each call consumes 5 credits. The signed URL expires after 15 minutes, and the platform permanently deletes the file about 60 minutes after upload (the cleanup sweep runs every 30 minutes); use "response": "binary" to stream the bytes directly.
{
"url": "https://example.com/landing",
"format": "mp4",
"fps": 30,
"speed": 800
}| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | required | The URL of the page to record. Must be a valid http(s) URL. |
width | integer | 1280 | Viewport width in pixels (320–1920). |
height | integer | 800 | Viewport height in pixels (240–1080). |
dark | boolean | false | Emulate the page’s dark color scheme. |
format | string | "mp4" | Video container: "mp4" (H.264, maximum compatibility) or "webm" (VP9). |
fps | integer | 30 | Output frame rate: 15, 24, 30, or 60. |
duration | integer | auto | Scroll duration in seconds (4–30). Omit to derive it automatically from page height and speed. |
speed | integer | 800 | Scroll speed in pixels per second (200–3000). Only used when duration is omitted; the derived duration is clamped to 4–30 seconds. |
wait | string | "domcontentloaded" | When to consider the page ready: domcontentloaded, load, or networkidle. |
delay | integer | 0 | Extra wait time after the ready event, in milliseconds (0–10000). |
noBanners | boolean | true | Automatically hide cookie banners and consent popups. |
response | string | "json" | "json" returns a temporary signed URL (valid for 15 minutes); "binary" streams the video bytes directly. |
A successful response looks like this:
{
"ok": true,
"storage": "minio",
"url": "https://snapapi.top/api/v1/files/eyJ...签名令牌",
"contentType": "video/mp4",
"bytes": 8421376,
"durationMs": 21400,
"frames": 642,
"width": 1280,
"height": 800,
"creditsUsed": 5
}| Field | Type | Description |
|---|---|---|
ok | boolean | Always true on success (false on errors). |
storage | string | Where the file was written: "minio" (platform storage) or "s3" (your own AWS S3 / Cloudflare R2 bucket). See the Cloud Storage section. |
url | string | null | Temporary signed link to the video (/api/v1/files/…), valid for 15 minutes; null when storage is "s3" (read bucket/key instead) or when platform storage is temporarily unavailable — retry with "response": "binary". |
bucket | string | null | Present only when storage is "s3": the destination bucket name (key, region, endpoint, and provider are also returned). |
key | string | null | Present only when storage is "s3": the object key written to your bucket. |
contentType | string | video/mp4 or video/webm. |
bytes | integer | Video file size in bytes. |
durationMs | integer | Wall-clock time of the call (page load + scroll recording + encoding), in milliseconds. |
frames | integer | Number of frames encoded. |
width | integer | Video width in pixels (equals the viewport width). |
height | integer | Video height in pixels (equals the viewport height). |
creditsUsed | integer | Credits charged for this call (5 per video). |
# JSON mode — get a signed URL (15-minute expiry)
curl -X POST https://snapapi.top/api/v1/video \
-H "Authorization: Bearer snap_your_api_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/landing"}'
# Binary mode — stream the MP4 directly to a file
curl -X POST https://snapapi.top/api/v1/video \
-H "Authorization: Bearer snap_your_api_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/landing", "response": "binary"}' \
--output scroll.mp4Recording happens in real time, so calls take roughly the scroll duration plus page load (typically 10–35 seconds). Very long pages scroll faster automatically — scroll time is always capped at 30 seconds.
Scheduled Screenshots API
Scheduled screenshots capture a URL on a recurring cron schedule (UTC) and push the result to your webhook URL as a signed JSON payload — no polling required. The feature is available on the Starter plan (up to 20 tasks) and the Pro plan (up to 50 tasks). Each successful run consumes the same credits as a regular screenshot; failed captures are not charged.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/v1/schedules | List all tasks owned by the current API key. The webhookSecret field is omitted. |
POST | /api/v1/schedules | Create a scheduled task. Returns 201 with the task object, including its webhookSecret. |
GET | /api/v1/schedules/{id} | Get a task's details, including the webhookSecret. |
PATCH | /api/v1/schedules/{id} | Update any field, or pause/resume with { "status": "paused" | "active" }. |
DELETE | /api/v1/schedules/{id} | Delete a task and its run history. |
POST | /api/v1/schedules/{id}/trigger | Trigger one run immediately. Returns 202 with a runId; the run executes in the background. |
GET | /api/v1/schedules/{id}/runs | List the 50 most recent runs — status, trigger, delivery attempts, last status code, and errors. |
Create a task
POST /api/v1/schedules accepts a JSON body with the following fields:
{
"name": "Daily homepage check",
"url": "https://example.com",
"cron": "0 9 * * *",
"webhookUrl": "https://your.app/hooks/snapapi",
"options": { "fullPage": true, "format": "jpeg", "width": 1280 }
}| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | required | The URL of the page to capture on every run. Must be a valid http(s) URL. |
cron | string | required | Standard 5-field cron expression in UTC (minute hour day-of-month month day-of-week), e.g. "0 9 * * 1-5". Minimum granularity is one minute. |
webhookUrl | string | required | Public http(s) URL that receives the result of every run. Localhost and private/internal IP addresses are rejected. |
name | string | "default" | Task name, up to 64 characters. |
options | object | {} | Screenshot options applied to every run: width, height, deviceScaleFactor, fullPage, dark, format, quality, wait, delay, selector, noBanners, clean — same meaning as the screenshot parameters above. |
The cron field uses standard five-field syntax — minute, hour, day of month, month, day of week — always evaluated in UTC:
"0 9 * * *"— every day at 09:00 UTC"0 9 * * 1-5"— Monday to Friday at 09:00 UTC"*/30 * * * *"— every 30 minutes
The create response (201) returns the full task object, including a per-task webhookSecret (prefixed with whsec_). The secret is also returned by the task-detail and update endpoints; list responses omit it.
curl -X POST https://snapapi.top/api/v1/schedules \
-H "Authorization: Bearer snap_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"cron": "0 9 * * *",
"webhookUrl": "https://your.app/hooks/snapapi"
}'Task object & responses
Create (201), detail, and update responses wrap one task as { ok: true, schedule: { … } }; the list endpoint wraps an array as { ok: true, schedules: [ … ] } (newest first, with webhookSecret omitted). The task object:
| Field | Type | Description |
|---|---|---|
id | string | Task ID (UUID). |
name | string | Task label. |
url | string | The page captured on every run. |
cron | string | 5-field cron expression in UTC. |
webhookUrl | string | Public URL that receives run results. |
webhookSecret | string | Per-task HMAC key (whsec_…) used to verify X-SnapAPI-Signature. Returned by create (201), task-detail, and update responses; list responses omit it. |
options | object | Screenshot options applied to every run. |
status | string | active or paused. |
pauseReason | string | null | Why the task is paused: user_paused, insufficient_credits, consecutive_failures, invalid_cron, or plan_required; null while active. |
nextRunAt | string | null | ISO 8601 timestamp of the next scheduled run (UTC); null while paused. |
lastRunAt | string | null | ISO 8601 timestamp of the most recent run. |
consecutiveFailures | integer | Consecutive failed runs; the task auto-pauses when this reaches 5. |
createdAt | string | ISO 8601 creation timestamp. |
updatedAt | string | ISO 8601 last-update timestamp. |
The remaining endpoints respond with:
POST …/{id}/trigger—202with{ ok: true, runId }; the run executes in the background.DELETE …/{id}—{ ok: true }with200.GET …/{id}/runs—{ ok: true, runs: [ … ] }with the 50 most recent runs (newest first). Each run object:
| Field | Type | Description |
|---|---|---|
id | string | Run ID (also sent as the X-SnapAPI-Delivery header). |
status | string | running → delivering → delivered | failed. |
trigger | string | scheduled (cron) or manual (trigger endpoint). |
deliveryAttempts | integer | Retries scheduled after failed deliveries (0–3): the initial delivery plus up to three retries, for at most four attempts total. |
bytes | integer | null | Capture size in bytes; null when the capture failed. |
durationMs | integer | null | Capture time in milliseconds; still recorded when capture or upload fails after starting, and null only when the run was aborted before any capture attempt (for example, insufficient credits). |
creditsUsed | integer | Credits charged for the run (0 when the capture failed). |
statusCode | integer | null | Last webhook HTTP status; null means a network-level failure (timeout, connection refused, blocked address). |
error | string | null | Failure reason (capture or delivery); null after a successful delivery. |
firedAt | string | ISO 8601 run start timestamp. |
finishedAt | string | null | ISO 8601 terminal timestamp; null while running/delivering. |
Webhook delivery
After every delivered run, SnapAPI POSTs a JSON payload to your webhookUrl. Runs aborted before capture — for example when the account is out of credits, which pauses the task — are not delivered. Deliveries that return a non-2xx status, time out after 10 seconds, or fail to connect are retried after 1, 5, and 30 minutes; the run is marked failed after the fourth attempt. Redirects are not followed.
Each delivery carries these headers:
X-SnapAPI-Event—screenshot.completedorscreenshot.failedX-SnapAPI-Delivery— the run IDX-SnapAPI-Timestamp— ISO 8601 delivery timestampX-SnapAPI-Signature—sha256=<HMAC>: the HMAC-SHA256 of the raw request body, keyed with the task'swebhookSecret
A completed delivery looks like this:
{
"event": "screenshot.completed",
"taskId": "b1e2c3d4-…",
"runId": "r9f8e7d6-…",
"url": "https://example.com",
"screenshotUrl": "https://snapapi.top/api/v1/files/…",
"contentType": "image/jpeg",
"bytes": 184320,
"durationMs": 1200,
"error": null,
"timestamp": "2026-08-31T09:00:01.234Z"
}For screenshot.failed events, screenshotUrl is null and error describes the cause (for example, an unreachable target page). The screenshotUrl link stays valid for 7 days and needs no API key — download or copy the image within that window.
Delivery payload fields:
| Field | Type | Description |
|---|---|---|
event | string | screenshot.completed or screenshot.failed. |
taskId | string | The scheduled task’s ID. |
runId | string | The run’s ID (matches X-SnapAPI-Delivery). |
url | string | The page URL that was captured. |
screenshotUrl | string | null | Signed link to the image, valid for 7 days without an API key; null for failed events. |
contentType | string | null | Image MIME type; null on failure. |
bytes | integer | null | Image size in bytes; null on failure. |
durationMs | integer | null | Capture time in milliseconds. Always present on screenshot.failed events (those fire only after capture was attempted, so it reports the elapsed time); null is reserved for aborted runs that deliver no event. |
error | string | null | Failure cause for screenshot.failed; null on success. |
timestamp | string | ISO 8601 delivery timestamp. |
Verify the signature before trusting a delivery (Node.js example):
const crypto = require("crypto");
function verifySignature(rawBody, signatureHeader, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Pausing, resuming, and failures
- Pause a task at any time with
PATCH /api/v1/schedules/{id}and body{ "status": "paused" }; resume with{ "status": "active" }. Resuming clears the failure count and recomputes the next run. - A task pauses automatically when your credits run out (
insufficient_credits) or after 5 consecutive failures (screenshot failures or exhausted webhook retries). Fix the cause, then resume it. - Use
POST /api/v1/schedules/{id}/triggerto fire a run on demand — handy for verifying your webhook end-to-end right after creating a task. - Poll
GET /api/v1/schedules/{id}/runsto inspect run status and delivery errors when debugging.
Batch Screenshots API
Batch jobs let you submit up to 50 URLs at once. Up to three URLs render concurrently in a background queue and, when the whole batch finishes, successful captures are packaged into a ZIP archive (with a manifest.json listing every URL and its result). You can either provide a webhookUrl to be notified on completion, or poll the job status. The feature is available on the Starter plan (up to 3 concurrent batches) and the Pro plan (up to 5); each successful capture consumes the same credits as a regular screenshot and failed URLs are not charged.
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /api/v1/batch | Submit a batch job (returns 201 with the job object) |
GET | /api/v1/batch | List your batch jobs (newest first) |
GET | /api/v1/batch/{id} | Job detail: per-URL items, live progress, and the signed zipUrl once packaged |
Submit a batch
POST /api/v1/batch accepts a JSON body. Every entry must already be a valid http(s) URL — entries with surrounding whitespace or blank entries are rejected with 400. Duplicates are removed and order is preserved:
{
"name": "Competitor homepage audit",
"urls": [
"https://example.com",
"https://example.org",
"https://example.net/pricing"
],
"webhookUrl": "https://your.app/hooks/snapapi",
"options": { "fullPage": true, "format": "png" }
}| Parameter | Type | Default | Description |
|---|---|---|---|
urls | string[] | required | 1–50 http(s) URLs; each entry must already be a valid URL with no surrounding whitespace (blank or malformed entries return 400). Duplicates are removed, order preserved. |
name | string | "default" | Optional label, up to 64 characters. |
options | object | {} | Same screenshot parameters as the Screenshot API (width, height, deviceScaleFactor, fullPage, dark, format, quality, wait, delay, selector, noBanners, clean, …) applied to every URL. |
webhookUrl | string | — | Optional public http(s) URL. When set, a signed batch.completed event is delivered after packaging; omit it for poll-only usage. |
The create response (201) returns the job object with status: "queued", a total count, and — when a webhook URL was supplied — a per-job webhookSecret (prefixed whsec_) used to verify deliveries. Submitting as a FREE account returns 403; exceeding the concurrent-batch quota returns 403; insufficient credits for the estimated batch cost returns 402.
curl -X POST https://snapapi.top/api/v1/batch \
-H "Authorization: Bearer snap_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"urls": ["https://example.com", "https://example.org"],
"webhookUrl": "https://your.app/hooks/snapapi"
}'Polling job status
GET /api/v1/batch/{id} returns the job, its items, and (once ready) zipUrl. Job status moves through queued → running → packaging → delivering → completed (or failed); a poll-only job with no webhookUrl skips delivering and completes straight from packaging. Each item reports pending, running, succeeded, or failed with an error message on failure. A 5-second poll interval is recommended while the job is active:
{
"ok": true,
"batch": {
"id": "b1e2c3d4-…",
"name": "Competitor homepage audit",
"status": "completed",
"total": 3,
"succeeded": 2,
"failed": 1,
"zipKey": "batch/2026/08/31/uuid.zip",
"zipBytes": 482130,
"createdAt": "2026-08-31T09:00:00.000Z",
"finishedAt": "2026-08-31T09:00:42.000Z"
},
"items": [
{ "index": 1, "url": "https://example.com", "status": "succeeded", "bytes": 210432, "error": null },
{ "index": 2, "url": "https://example.org", "status": "succeeded", "bytes": 271698, "error": null },
{ "index": 3, "url": "https://example.net/pricing", "status": "failed", "bytes": null, "error": "net::ERR_NAME_NOT_RESOLVED" }
],
"zipUrl": "https://snapapi.top/api/v1/files/…"
}Response wrapping: create (201) returns { ok: true, batch: { … } }; list returns { ok: true, batches: [ … ] } (newest first, with webhookSecret omitted); detail returns the shape above — { ok, batch, items, zipUrl }. The batch job object:
| Field | Type | Description |
|---|---|---|
id | string | Job ID (UUID). |
name | string | Job label. |
status | string | queued → running → packaging → delivering → completed, or failed (terminal). Poll-only jobs (no webhookUrl) skip delivering and move straight from packaging to completed. |
options | object | Screenshot options applied to every URL. |
webhookUrl | string | null | Notification URL, or null for poll-only jobs. |
webhookSecret | string | null | Per-job HMAC key (whsec_…) for verifying batch.completed deliveries. Returned by create (201) and present in job detail; omitted from list responses. null when no webhookUrl was given. |
total | integer | Number of URLs in the batch (after de-duplication). |
succeeded | integer | Items captured successfully so far. |
failed | integer | Items that failed so far. |
zipKey | string | null | Internal storage key of the ZIP archive; null until packaging finishes or when every URL failed. |
zipBytes | integer | null | ZIP archive size in bytes; null until packaged. |
deliveryAttempts | integer | Webhook delivery attempts made so far (up to 4). |
statusCode | integer | null | Last webhook HTTP status; null for poll-only jobs or network-level failures. |
error | string | null | Failure reason (packaging or delivery); null otherwise. |
startedAt | string | null | ISO 8601 timestamp when rendering started. |
finishedAt | string | null | ISO 8601 terminal timestamp. |
createdAt | string | ISO 8601 creation timestamp. |
updatedAt | string | ISO 8601 last-update timestamp. |
Each entry in items:
| Field | Type | Description |
|---|---|---|
id | string | Item ID (UUID). |
index | integer | 1-based position in the submitted URL list (also used for ZIP file naming). |
url | string | The captured URL. |
status | string | pending → running → succeeded | failed. |
contentType | string | null | MIME type of the capture; null until succeeded. |
bytes | integer | null | Capture size in bytes; null until succeeded. |
durationMs | integer | null | Capture time in milliseconds; null until finished. |
creditsUsed | integer | Credits charged for this item (0 while pending or on failure). |
error | string | null | Failure reason on failed items; null otherwise. |
Webhook notification
When packaging finishes, SnapAPI POSTs a batch.completed event to your webhook URL, signed exactly like scheduled-task deliveries (X-SnapAPI-Signature: sha256=<HMAC> keyed with the job's webhookSecret; verify with the same timing-safe comparison shown in the Scheduled Screenshots section). Failed deliveries retry after 1, 5, and 30 minutes. The zipUrl is null if every URL failed (no archive is produced):
{
"event": "batch.completed",
"batchId": "b1e2c3d4-…",
"name": "Competitor homepage audit",
"total": 3,
"succeeded": 2,
"failed": 1,
"zipUrl": "https://snapapi.top/api/v1/files/…",
"zipBytes": 482130,
"createdAt": "2026-08-31T09:00:00.000Z",
"finishedAt": "2026-08-31T09:00:42.000Z",
"timestamp": "2026-08-31T09:00:42.500Z"
}Webhook payload fields:
| Field | Type | Description |
|---|---|---|
event | string | Always batch.completed. |
batchId | string | The batch job’s ID (matches X-SnapAPI-Delivery). |
name | string | Job label. |
total | integer | Total URL count. |
succeeded | integer | Successful captures. |
failed | integer | Failed URLs. |
zipUrl | string | null | Signed link to the ZIP archive, valid for 7 days without an API key; null when every URL failed (no archive produced). |
zipBytes | integer | null | ZIP archive size in bytes; null when no archive was produced. |
createdAt | string | ISO 8601 job creation timestamp. |
finishedAt | string | null | ISO 8601 packaging completion timestamp. |
timestamp | string | ISO 8601 delivery timestamp. |
ZIP archive contents
manifest.json— the first entry: batch id, totals, and a per-URL record (index, url, status, file name, bytes, duration, error).- One image per successful capture, named
NNN-hostname.extwith the 1-based submission index (for example001-example.com.png), so file order matches your input order. - The
zipUrllink needs no API key and stays valid for 7 days; archives are deleted afterwards.
Visual Diff API
POST /api/v1/diff compares two images at the pixel level (decoded with sharp, matched with pixelmatch) and reports how much changed. It is available on the Starter and Pro plans (FREE returns 403). Provide a baseline image and choose how to supply the current one:
- Image vs image — set
currentImageUrlto another image URL. Both images are downloaded and compared; this costs one diff credit. - Image vs live page — set
urlinstead. SnapAPI captures the page live (always PNG) and compares it against the baseline; this costs one screenshot credit plus one diff credit. - Upload files — send
multipart/form-datawithbaselineand/orcurrentfile fields. Uploads mix freely with URLs and live captures (e.g. an uploaded baseline plusurl); this costs one diff credit, plus one screenshot credit when usingurl.
External image URLs pass through SSRF protection; an unreachable URL, a non-image response, or an undecodable image returns 422. With JSON, exactly one of currentImageUrl or url is required; with multipart, provide exactly one of current, currentImageUrl, or url, and exactly one of baseline or baselineImageUrl.
Uploaded files accept multipart/form-data only: each file is capped at 25 MiB and the whole request at ~52 MiB (oversize returns 413); an unsupported Content-Type returns 415; a file with an explicitly non-image type (e.g. text/plain) or a malformed field returns 400. Files with no type or application/octet-stream are accepted and decoded by sharp, returning 422 when the bytes are not a real image. Scalar options are sent as text fields (numbers as numeric strings, booleans as "true"/"false").
Set produceDiffImage to true to receive a side-by-side JPEG: the baseline image on the left and the current image on the right, with changed regions marked by red boxes on the right half. It is returned as a 15-minute signed URL (diffImage.url) you can open directly or download; no extra credit is charged. The image is always produced when requested, even when nothing changed (a side-by-side image with no red boxes).
curl -X POST https://snapapi.top/api/v1/diff \
-H "Authorization: Bearer snap_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"baselineImageUrl": "https://cdn.example.com/home/baseline.png",
"url": "https://example.com",
"threshold": 0.01,
"produceDiffImage": true,
"fullPage": true
}'| Parameter | Type | Default | Description |
|---|---|---|---|
baselineImageUrl | string | required* | URL of the baseline image to compare against. Downloaded through SSRF protection; must be a decodable image. *Required in JSON mode; with multipart you may instead upload the baseline file. |
baseline | file | — | multipart/form-data only: the baseline image file. Provide exactly one of baselineImageUrl or baseline. Max 25 MiB; must be a decodable image (PNG/JPEG/WebP). |
currentImageUrl | string | — | URL of the current image to compare. Provide exactly one of currentImageUrl, current, or url. |
current | file | — | multipart/form-data only: the current image file. Provide exactly one of currentImageUrl, current, or url. Max 25 MiB; must be a decodable image (PNG/JPEG/WebP). |
url | string | — | Page URL to capture live (PNG) and then compare against the baseline. Provide exactly one of currentImageUrl, current, or url. Adds one screenshot credit on top of the diff credit. |
threshold | number | 0.01 | Changed-pixel ratio (0–1) at or above which hasChanged is true. 0.01 means 1% of pixels differ. |
produceDiffImage | boolean | false | When true, also render a JPEG side-by-side comparison image (quality 80) — baseline on the left, current on the right with red boxes around changed regions — upload it, and return a 15-minute signed download URL in diffImage. No extra credit; always returned when requested (a side-by-side image with no red boxes means no change). null on storage failure. |
width / height / fullPage / dark / clean / wait / delay | — | — | Screenshot options, identical to the Screenshot API; applied only when using url (live capture). clean requires a paid plan. |
Uploading image files uses multipart form fields (files and URLs may be mixed):
# both images uploaded curl -X POST https://snapapi.top/api/v1/diff \ -H "Authorization: Bearer snap_your_api_key" \ -F "baseline=@/path/to/baseline.png" \ -F "current=@/path/to/current.png" \ -F "threshold=0.01" \ -F "produceDiffImage=true" # uploaded baseline compared against a live page capture curl -X POST https://snapapi.top/api/v1/diff \ -H "Authorization: Bearer snap_your_api_key" \ -F "baseline=@/path/to/baseline.png" \ -F "url=https://example.com" \ -F "fullPage=true"
A successful response looks like this:
{
"ok": true,
"threshold": 0.01,
"changePercentage": 0.0231,
"changePercent": 2.31,
"hasChanged": true,
"diffPixels": 36210,
"totalPixels": 1568000,
"width": 1280,
"height": 1225,
"diffImage": {
"url": "https://snapapi.top/api/v1/files/eyJ…signature",
"contentType": "image/jpeg",
"bytes": 183420,
"expiresInSeconds": 900
},
"baseline": { "source": "url", "bytes": 201432, "contentType": "image/png" },
"current": { "source": "capture", "bytes": 207880, "contentType": "image/png", "durationMs": 1180 },
"creditsUsed": 2
}| Field | Type | Description |
|---|---|---|
ok | boolean | Always true on success (false on errors). |
threshold | number | The threshold used for the comparison (0–1). |
changePercentage | number | Ratio of changed pixels to total pixels (0–1). |
changePercent | number | Changed pixels as a percentage, rounded to two decimals (e.g. 1.25). |
hasChanged | boolean | true when changePercentage meets or exceeds threshold. |
diffPixels | integer | Number of pixels that differ between the two images. |
totalPixels | integer | Total pixels compared (width × height after normalization). |
width / height | integer | Dimensions of the normalized images that were compared. |
diffImage | object | null | Present only when produceDiffImage=true: { url, contentType: "image/jpeg", bytes, expiresInSeconds: 900 }. url is a signed link to the side-by-side comparison JPEG (baseline left; current right with red boxes on changes), valid for 15 minutes and usable directly in <img src> or a browser download. null when not requested or on storage failure. |
baseline / current | object | Metadata for each image: { source: "url" | "upload" | "capture", bytes, contentType }. "upload" marks a multipart file, "capture" a live page screenshot (current only, also includes durationMs). |
creditsUsed | integer | Credits charged: one diff credit, plus one screenshot credit when using url (live capture). |
The same comparison is available to AI assistants through the MCP diff_pages tool, which captures both pages and compares them in one call; pass includeDiffImage to get the side-by-side comparison image (red boxes on changes) as a 15-minute signed URL (diffImageUrl).
Visual Monitors API
A monitor captures a URL on a recurring cron schedule (UTC), compares each capture against a saved baseline at the pixel level, and — when the changed-pixel ratio meets your threshold — sends a signed monitor.changed alert to your webhook with links to the baseline, current, and diff images. Monitors are available on the Pro plan (up to 10 monitorsper account; other plans return 403). Each run performs one screenshot plus one comparison, so it consumes one screenshot credit and one diff credit; failed runs are not charged.
How baselines work
The first run establishes the baseline and never alerts. After that, the baselineMode decides when the reference image moves forward:
fixed(default) — the baseline never changes; every run is compared against the original. Best for detecting any drift from a known-good state.auto(rolling) — each capture becomes the new baseline, so you are alerted only about changes since the previous run.manual— the baseline stays fixed until you confirm a new one from the dashboard.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/v1/monitors | List all monitors owned by the current API key. The webhookSecret field is omitted. |
POST | /api/v1/monitors | Create a monitor. Returns 201 with the monitor object, including its webhookSecret when a webhookUrl was supplied. |
GET | /api/v1/monitors/{id} | Get a monitor's details, including the webhookSecret. |
PATCH | /api/v1/monitors/{id} | Update any field, or pause/resume with { "status": "paused" | "active" }. Send { "webhookUrl": "" } to clear a previously configured webhook; sending null is rejected with 400. |
DELETE | /api/v1/monitors/{id} | Delete a monitor and its run history. |
POST | /api/v1/monitors/{id}/trigger | Run the comparison immediately. Returns 202 with a runId; the run executes in the background. |
GET | /api/v1/monitors/{id}/runs | List the 50 most recent runs — change ratio, alert flag, delivery status, and errors. |
Create a monitor
POST /api/v1/monitors accepts a JSON body:
{
"name": "Marketing homepage",
"url": "https://example.com",
"cron": "0 */6 * * *",
"webhookUrl": "https://your.app/hooks/snapapi",
"baselineMode": "fixed",
"threshold": 0.01,
"options": { "fullPage": true, "width": 1280 }
}| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | required | The page URL to capture and compare on every run. Must be a valid http(s) URL. |
cron | string | required | Standard 5-field cron expression in UTC, e.g. "0 */6 * * *" (every 6 hours). Minimum granularity is one minute. |
webhookUrl | string | — | Optional public http(s) URL that receives monitor.changed / monitor.failed alerts. Localhost and private addresses are rejected. For dashboard-only alerts, either omit the field or send an empty string ""; sending an explicit null is rejected with 400. |
name | string | — | Monitor name, up to 64 characters. |
options | object | {} | Screenshot options applied to every run (width, height, fullPage, dark, format, wait, delay, clean, …) — same meaning as the screenshot parameters. Images are always captured as PNG for comparison. |
baselineMode | string | "fixed" | How the reference baseline is maintained: fixed (never auto-updated), auto (rolling — each run becomes the new baseline), or manual (you confirm a new baseline in the dashboard). |
threshold | number | 0.01 | Changed-pixel ratio (0–1) that triggers an alert. 0.01 means alert when at least 1% of pixels differ. |
The create response (201) returns { ok: true, monitor: { … } }, including a per-monitor webhookSecret (prefixed whsec_) when a webhook URL was supplied. The list endpoint returns { ok: true, monitors: [ … ] } (newest first, secret omitted); detail returns { ok: true, monitor }. The monitor object:
| Field | Type | Description |
|---|---|---|
id | string | Monitor ID (UUID). |
name | string | Monitor label. |
url | string | The page captured and compared on every run. |
cron | string | 5-field cron expression in UTC. |
webhookUrl | string | null | Alert destination, or null when alerts are dashboard-only. |
webhookSecret | string | null | Per-monitor HMAC key (whsec_…) for verifying X-SnapAPI-Signature. Returned by create (201), detail, and update responses; omitted from list responses. null when no webhookUrl is set. |
options | object | Screenshot options applied to every run. |
baselineMode | string | fixed, auto, or manual — see the create parameters. |
threshold | number | Changed-pixel ratio that triggers an alert. |
status | string | active or paused. |
pauseReason | string | null | Why the monitor is paused: user_paused, insufficient_credits, consecutive_failures, invalid_cron, or plan_required; null while active. |
baselineUpdatedAt | string | null | ISO 8601 timestamp of the current baseline; null until the first run establishes one. |
nextRunAt | string | null | ISO 8601 timestamp of the next scheduled comparison (UTC); null while paused. |
lastRunAt | string | null | ISO 8601 timestamp of the most recent run. |
consecutiveFailures | integer | Consecutive failed runs; the monitor auto-pauses when this reaches 5. |
createdAt / updatedAt | string | ISO 8601 creation and last-update timestamps. |
POST …/{id}/trigger returns 202 with { ok: true, runId } to run a comparison immediately. GET …/{id}/runs returns { ok: true, runs: [ … ] } (50 most recent). Each run object:
| Field | Type | Description |
|---|---|---|
id | string | Run ID (also sent as the X-SnapAPI-Delivery header on alerts). |
status | string | running → delivering → delivered | failed. The first run (and any run that rebuilt a missing baseline) establishes the baseline and never alerts. |
trigger | string | scheduled (cron) or manual (trigger endpoint). |
changePercentage | number | null | Changed-pixel ratio (0–1); null on the baseline-setting run or on failure. |
hasChanged | boolean | null | Whether the change met the threshold; null on the baseline run or on failure. |
threshold | number | null | Alert threshold in effect for the run. |
baselineUpdated | boolean | true when this run established or rolled the baseline (first run, auto mode, or a rebuilt baseline). |
bytes | integer | null | Capture size in bytes; null when the capture failed. |
durationMs | integer | null | Capture-and-compare time in milliseconds; still recorded on failed runs when a webhook is configured, and null only when no timing was captured (a dashboard-only monitor’s failure or an internal crash). |
creditsUsed | integer | Credits charged (one screenshot + one diff); 0 when the capture failed. |
deliveryAttempts | integer | Retries scheduled after failed deliveries (0–3): the initial delivery plus up to three retries, for at most four attempts total. |
statusCode | integer | null | Last webhook HTTP status; null for dashboard-only monitors or network-level failures. |
error | string | null | Failure cause (capture, storage, compare, or delivery); null after success. |
firedAt / finishedAt | string | ISO 8601 run-start and terminal timestamps (finishedAt is null while running). |
Change-alert webhooks
When a run detects a change past the threshold (or fails), SnapAPI POSTs an event to your webhookUrl, signed exactly like scheduled-task deliveries — X-SnapAPI-Signature: sha256=<HMAC> keyed with the monitor's webhookSecret; verify it with the same timing-safe comparison shown in the Scheduled Screenshots section. The X-SnapAPI-Event header is monitor.changed or monitor.failed. Failed deliveries retry after 1, 5, and 30 minutes.
{
"event": "monitor.changed",
"taskId": "m3n4o5p6-…",
"runId": "r9f8e7d6-…",
"url": "https://example.com",
"baselineUrl": "https://snapapi.top/api/v1/files/…",
"currentUrl": "https://snapapi.top/api/v1/files/…",
"diffUrl": "https://snapapi.top/api/v1/files/…",
"changePercentage": 0.0231,
"changePercent": 2.31,
"hasChanged": true,
"threshold": 0.01,
"baselineUpdated": false,
"durationMs": 1340,
"error": null,
"timestamp": "2026-09-06T06:00:02.100Z"
}The image links need no API key and stay valid for 7 days. On monitor.failed, currentUrl and diffUrl are null; baselineUrl is still signed and returned when the monitor already has a baseline (it is null only when a failure happens before the first baseline exists). The threshold and durationMs fields are also populated on failure. Run history and comparison images are retained for 90 days on the Pro plan. Payload fields:
| Field | Type | Description |
|---|---|---|
event | string | monitor.changed (a change met the threshold) or monitor.failed (the run errored). |
taskId | string | The monitor’s ID. |
runId | string | The run’s ID (matches X-SnapAPI-Delivery). |
url | string | The page URL that was captured. |
baselineUrl | string | null | Signed link to the baseline image, valid for 7 days without an API key. Always present on changed events; also present on failed events once a baseline has been established, and null only for failures before the first baseline. |
currentUrl | string | null | Signed link to the current capture, valid for 7 days; null on failure. |
diffUrl | string | null | Signed link to the side-by-side comparison JPEG (baseline left, current right with red boxes on changes), valid for 7 days; null when nothing changed or on failure. |
changePercentage | number | null | Changed-pixel ratio (0–1); null on failure. |
changePercent | number | null | Changed pixels as a percentage, rounded to two decimals; null on failure. |
hasChanged | boolean | null | Whether the change met the threshold; null on failure. |
threshold | number | Alert threshold in effect for the monitor — present on both changed and failed events. |
baselineUpdated | boolean | true when this run rolled or rebuilt the baseline. |
durationMs | integer | null | Capture-and-compare time in milliseconds; failed events still report the elapsed attempt time, and it is null only for un-timed internal crashes. |
error | string | null | Failure cause for monitor.failed; null on changed events. |
timestamp | string | ISO 8601 delivery timestamp. |
Pausing, resuming, and failures
- Pause with
PATCH /api/v1/monitors/{id}and body{ "status": "paused" }; resume with{ "status": "active" }. Resuming clears the failure count and recomputes the next run. - A monitor pauses automatically when your credits run out (
insufficient_credits) or after 5 consecutive failures. Fix the cause, then resume it. - Monitors run entirely on platform storage so comparisons stay fast and reliable — connecting your own cloud bucket (below) does not move monitor baselines or images.
Cloud Storage Delivery
On the Pro plan you can deliver captures directly to your own cloud bucket — Amazon S3 or Cloudflare R2 (any S3-compatible endpoint) — instead of downloading them through SnapAPI. Configure the provider, bucket, region/endpoint, and access credentials in the dashboard; the secret key is encrypted with AES-256-GCM before storage and is never returned by the API (the access key ID is shown so you can identify the configured credential).
Once a bucket is connected:
- Synchronous screenshots and scroll videos (
response: "json") are written straight to your bucket. The response reportsstorage: "s3"with thebucketandkey(plusregion,endpoint,provider) so you can locate the object; the platform-signedurlisnullin this case. Objects are stored under<keyPrefix>/<yyyy>/<mm>/<dd>/<uuid>.<ext>—snapapi/…by default (the prefix is customizable in the dashboard), withscheduled/andbatch/sub-prefixes for those deliveries. - Scheduled screenshots and batch ZIP archives are uploaded directly to your bucket, and the
screenshotUrl/zipUrlin webhook payloads are presigned links against your own bucket (valid for 7 days) — downloads never transit the platform. - Monitors keep using platform storage: the baseline is a server-side working set that is read back on every comparison, so monitor images are not written to your bucket.
If an upload to your bucket fails (bad credentials, missing permission, or wrong bucket name), the call returns 422 with a clear error and does not fall back to platform storage — so you never mistake a missing object for a successful delivery. Synchronous calls that fail this way are not charged; async runs are marked failed. Disconnecting or changing the bucket in the dashboard affects new captures only; already-delivered objects stay in whatever bucket they were written to.
Errors
Errors return JSON with an error message:
{ "ok": false, "error": "insufficient credits" }| Field | Type | Description |
|---|---|---|
ok | boolean | Always false on errors. |
error | string | Human-readable error message. |
details | object | Present only on 400 validation errors: Zod flatten() output (formErrors / fieldErrors) describing exactly which parameter was rejected. |
code | string | Present only on 422 errors: a machine-readable failure code — blocked_url, target_unreachable, rendering_failed, blank_capture, image_decode_failed, or storage_unavailable. |
| Status | Meaning |
|---|---|
400 | Invalid request body — a parameter is missing or has an invalid value. |
401 | Missing or invalid API key — the Authorization header is absent or the key is invalid / revoked. |
402 | Insufficient credits — your account is out of credits. Upgrade a plan or wait for the monthly reset. |
403 | Plan restriction — either the feature is not available on your plan (content extraction, visual diff, scheduled and batch screenshots require Starter or Pro; scroll video, monitors and cloud storage require Pro), or a plan quota has been exceeded (for example, scheduled tasks: Starter 20, Pro 50; monitors: Pro 10). Upgrade a plan to get access or raise the limit. |
413 | Request body too large — the JSON body exceeds 1 MB, or an uploaded diff image exceeds the 25 MiB per-file limit. |
422 | Target page processing failed — the page could not be captured or extracted (for example, the URL is unreachable or timed out). This indicates a problem with the target page, not the API service. The body includes a machine-readable code (blocked_url, target_unreachable, rendering_failed, blank_capture, image_decode_failed, or storage_unavailable). |
429 | Rate limit exceeded — too many requests per second. The Retry-After header indicates when to retry. |
503 | Render capacity temporarily exhausted — all browser workers are busy. Retry after the Retry-After header (seconds). Calls that fail this way are not charged. |
Code examples
cURL
curl -X POST https://snapapi.top/api/v1/screenshot \
-H "Authorization: Bearer snap_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"fullPage": true,
"format": "jpeg",
"quality": 80,
"dark": true
}'JavaScript (fetch)
const res = await fetch("https://snapapi.top/api/v1/screenshot", {
method: "POST",
headers: {
"Authorization": "Bearer snap_your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://example.com",
fullPage: true,
format: "png",
}),
});
const data = await res.json();
console.log(data.url); // temporary link to the screenshotPython (requests)
import requests
res = requests.post(
"https://snapapi.top/api/v1/screenshot",
headers={"Authorization": "Bearer snap_your_api_key"},
json={
"url": "https://example.com",
"fullPage": True,
"format": "png",
},
)
res.raise_for_status()
print(res.json()["url"])MCP Server
SnapAPI speaks the Model Context Protocol (MCP), so AI assistants can call it as a tool. Connect any MCP-compatible client to the endpoint below using the same Bearer API key:
https://snapapi.top/api/mcp
Tools
| Tool | Description |
|---|---|
take_screenshot | Capture a screenshot of any URL. Exposes the same option set as the REST API but with camelCase names — darkMode, waitUntil, delayMs, removeCookieBanners, cleanMode (plus width, height, deviceScaleFactor, fullPage, format, quality, selector); there is no response option, the image is always returned inline as a content block. Consumes one credit. |
extract_page_content | Extract a page’s article content as Markdown or raw rendered HTML (formats) plus structured metadata, outline, and links; maxChars caps text length. Consumes one credit per call. Requires the Starter or Pro plan. |
extract_page_metadata | Read the page’s title, description, and other metadata. Consumes one credit per call. Requires the Starter or Pro plan. |
diff_pages | Capture screenshots of two pages (baseline and current) and compare them at the pixel level. Returns the changed-pixel percentage and whether it exceeds the threshold; includeDiffImage returns a 15-minute signed URL of the side-by-side comparison JPEG (baseline left; current right with red boxes on changes). Consumes two screenshots plus one diff credit. Requires the Starter or Pro plan. |
account_info | Check your credit balance, account status, tier rate limits, and per-action credit prices. Free — it does not consume credits. |
Claude Desktop
Add the server to your claude_desktop_config.json:
{
"mcpServers": {
"snapapi": {
"url": "https://snapapi.top/api/mcp",
"headers": {
"Authorization": "Bearer snap_your_api_key"
}
}
}
}Cursor & other clients
In Cursor, add a new MCP server using the streamable-HTTP / HTTP transport with the URL and Authorization header above. Other MCP-compatible clients work the same way.
Once connected, just ask your agent — for example, “take a full-page screenshot of example.com in dark mode” — and it will call the tool for you.