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

ParameterTypeDefaultDescription
urlstringrequiredThe URL of the page to capture. Must be a valid http(s) URL.
widthinteger1280Viewport width in pixels (320–3840).
heightinteger800Viewport height in pixels (240–2160).
deviceScaleFactorinteger1Device scale factor, 1–3. Use 2 or 3 for crisp retina-quality images.
fullPagebooleanfalseCapture 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.
darkbooleanfalseEmulate the page’s dark color scheme.
formatstring"png"Output format: png, jpeg, webp, or pdf.
qualityinteger85Quality from 1 to 100 for JPEG and WebP. Ignored for PNG and PDF.
waitstring"domcontentloaded"When to consider the page ready: domcontentloaded, load, or networkidle.
delayinteger0Extra wait time after the ready event, in milliseconds (0–10000).
selectorstring—CSS selector of a single element to capture, e.g. "#main-card".
noBannersbooleantrueAutomatically hide cookie banners and consent popups.
cleanbooleanfalseClean 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).
responsestring"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 with delay for 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 with quality.
  • webp — modern format, smaller than PNG or JPEG at the same quality; tune with quality.
  • 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
}
FieldTypeDescription
okbooleanAlways true on success (false on errors).
storagestringWhere 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.
urlstring | nullTemporary 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").
bucketstring | nullPresent only when storage is "s3": the destination bucket name. Together with key, region, endpoint, and provider it locates the object in your own bucket.
keystring | nullPresent 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.
regionstring | nullPresent only when storage is "s3": the bucket region (empty for R2).
endpointstring | nullPresent only when storage is "s3": the custom S3-compatible endpoint, if configured (for example Cloudflare R2).
providerstring | nullPresent only when storage is "s3": the storage provider code ("aws" or "r2").
contentTypestringMIME type of the result: image/png, image/jpeg, image/webp, or application/pdf.
bytesintegerFile size in bytes.
durationMsintegerServer-side capture time in milliseconds.
creditsUsedintegerCredits 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.png

Content 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
}
ParameterTypeDefaultDescription
urlstringrequiredThe URL of the page to extract. Must be a valid http(s) URL.
formatsstring[]["markdown","structured"]Any of "html" (raw rendered HTML, large), "markdown" (article body as Markdown) and "structured" (metadata, article, outline, links).
waitstring"load"When the page is considered ready: domcontentloaded, load, or networkidle. Use networkidle for JavaScript-heavy SPAs.
delayinteger0Extra wait time after the ready event, in milliseconds (0–10000).
maxCharsinteger100000Maximum characters per text field (1000–2000000); longer fields are truncated and truncated is set to true.
noBannersbooleantrueRemove cookie banners and consent popups before extraction.
cleanbooleanfalseClean 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:

FieldTypeDescription
okbooleanAlways true on success (false on errors).
urlstringThe requested URL.
finalUrlstringThe URL after any redirects.
statusCodeintegerTarget page’s HTTP status code; 0 when the navigation produced no response.
contentobjectPresent only when formats includes markdown or html: content.markdown and/or content.html strings. Omitted entirely for structured-only requests.
structuredobjectPresent when formats includes structured (the default): metadata, article, outline, links — see the Structured content table below. Omitted for html-only requests.
truncatedbooleantrue if any text field was cut at the maxChars limit.
bytesintegerUTF-8 size of the returned content in bytes.
durationMsintegerServer-side extraction time in milliseconds.
creditsUsedintegerCredits charged for this call.

Structured content

The structured object is included whenever formats contains structured (the default). Its fields are:

FieldTypeDescription
structured.metadataobjectSEO/Open Graph metadata: url, title, description, keywords, author, ogImage, ogTitle, ogDescription, favicon, lang, charset, viewport. Image/favicon URLs are resolved to absolute.
structured.articleobject | nullReadability 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.outlinearrayPage headings as { level, text, id } objects (h1–h6, up to 100).
structured.linksarrayOutbound 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
}
ParameterTypeDefaultDescription
urlstringrequiredThe URL of the page to record. Must be a valid http(s) URL.
widthinteger1280Viewport width in pixels (320–1920).
heightinteger800Viewport height in pixels (240–1080).
darkbooleanfalseEmulate the page’s dark color scheme.
formatstring"mp4"Video container: "mp4" (H.264, maximum compatibility) or "webm" (VP9).
fpsinteger30Output frame rate: 15, 24, 30, or 60.
durationintegerautoScroll duration in seconds (4–30). Omit to derive it automatically from page height and speed.
speedinteger800Scroll speed in pixels per second (200–3000). Only used when duration is omitted; the derived duration is clamped to 4–30 seconds.
waitstring"domcontentloaded"When to consider the page ready: domcontentloaded, load, or networkidle.
delayinteger0Extra wait time after the ready event, in milliseconds (0–10000).
noBannersbooleantrueAutomatically hide cookie banners and consent popups.
responsestring"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
}
FieldTypeDescription
okbooleanAlways true on success (false on errors).
storagestringWhere the file was written: "minio" (platform storage) or "s3" (your own AWS S3 / Cloudflare R2 bucket). See the Cloud Storage section.
urlstring | nullTemporary 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".
bucketstring | nullPresent only when storage is "s3": the destination bucket name (key, region, endpoint, and provider are also returned).
keystring | nullPresent only when storage is "s3": the object key written to your bucket.
contentTypestringvideo/mp4 or video/webm.
bytesintegerVideo file size in bytes.
durationMsintegerWall-clock time of the call (page load + scroll recording + encoding), in milliseconds.
framesintegerNumber of frames encoded.
widthintegerVideo width in pixels (equals the viewport width).
heightintegerVideo height in pixels (equals the viewport height).
creditsUsedintegerCredits 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.mp4

Recording 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

MethodPathDescription
GET/api/v1/schedulesList all tasks owned by the current API key. The webhookSecret field is omitted.
POST/api/v1/schedulesCreate 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}/triggerTrigger one run immediately. Returns 202 with a runId; the run executes in the background.
GET/api/v1/schedules/{id}/runsList 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 }
}
ParameterTypeDefaultDescription
urlstringrequiredThe URL of the page to capture on every run. Must be a valid http(s) URL.
cronstringrequiredStandard 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.
webhookUrlstringrequiredPublic http(s) URL that receives the result of every run. Localhost and private/internal IP addresses are rejected.
namestring"default"Task name, up to 64 characters.
optionsobject{}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:

FieldTypeDescription
idstringTask ID (UUID).
namestringTask label.
urlstringThe page captured on every run.
cronstring5-field cron expression in UTC.
webhookUrlstringPublic URL that receives run results.
webhookSecretstringPer-task HMAC key (whsec_…) used to verify X-SnapAPI-Signature. Returned by create (201), task-detail, and update responses; list responses omit it.
optionsobjectScreenshot options applied to every run.
statusstringactive or paused.
pauseReasonstring | nullWhy the task is paused: user_paused, insufficient_credits, consecutive_failures, invalid_cron, or plan_required; null while active.
nextRunAtstring | nullISO 8601 timestamp of the next scheduled run (UTC); null while paused.
lastRunAtstring | nullISO 8601 timestamp of the most recent run.
consecutiveFailuresintegerConsecutive failed runs; the task auto-pauses when this reaches 5.
createdAtstringISO 8601 creation timestamp.
updatedAtstringISO 8601 last-update timestamp.

The remaining endpoints respond with:

  • POST …/{id}/trigger — 202 with { ok: true, runId }; the run executes in the background.
  • DELETE …/{id} — { ok: true } with 200.
  • GET …/{id}/runs — { ok: true, runs: [ … ] } with the 50 most recent runs (newest first). Each run object:
FieldTypeDescription
idstringRun ID (also sent as the X-SnapAPI-Delivery header).
statusstringrunning → delivering → delivered | failed.
triggerstringscheduled (cron) or manual (trigger endpoint).
deliveryAttemptsintegerRetries scheduled after failed deliveries (0–3): the initial delivery plus up to three retries, for at most four attempts total.
bytesinteger | nullCapture size in bytes; null when the capture failed.
durationMsinteger | nullCapture 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).
creditsUsedintegerCredits charged for the run (0 when the capture failed).
statusCodeinteger | nullLast webhook HTTP status; null means a network-level failure (timeout, connection refused, blocked address).
errorstring | nullFailure reason (capture or delivery); null after a successful delivery.
firedAtstringISO 8601 run start timestamp.
finishedAtstring | nullISO 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.completed or screenshot.failed
  • X-SnapAPI-Delivery — the run ID
  • X-SnapAPI-Timestamp — ISO 8601 delivery timestamp
  • X-SnapAPI-Signature — sha256=<HMAC>: the HMAC-SHA256 of the raw request body, keyed with the task's webhookSecret

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:

FieldTypeDescription
eventstringscreenshot.completed or screenshot.failed.
taskIdstringThe scheduled task’s ID.
runIdstringThe run’s ID (matches X-SnapAPI-Delivery).
urlstringThe page URL that was captured.
screenshotUrlstring | nullSigned link to the image, valid for 7 days without an API key; null for failed events.
contentTypestring | nullImage MIME type; null on failure.
bytesinteger | nullImage size in bytes; null on failure.
durationMsinteger | nullCapture 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.
errorstring | nullFailure cause for screenshot.failed; null on success.
timestampstringISO 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}/trigger to fire a run on demand — handy for verifying your webhook end-to-end right after creating a task.
  • Poll GET /api/v1/schedules/{id}/runs to 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

MethodPathDescription
POST/api/v1/batchSubmit a batch job (returns 201 with the job object)
GET/api/v1/batchList 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" }
}
ParameterTypeDefaultDescription
urlsstring[]required1–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.
namestring"default"Optional label, up to 64 characters.
optionsobject{}Same screenshot parameters as the Screenshot API (width, height, deviceScaleFactor, fullPage, dark, format, quality, wait, delay, selector, noBanners, clean, …) applied to every URL.
webhookUrlstring—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:

FieldTypeDescription
idstringJob ID (UUID).
namestringJob label.
statusstringqueued → running → packaging → delivering → completed, or failed (terminal). Poll-only jobs (no webhookUrl) skip delivering and move straight from packaging to completed.
optionsobjectScreenshot options applied to every URL.
webhookUrlstring | nullNotification URL, or null for poll-only jobs.
webhookSecretstring | nullPer-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.
totalintegerNumber of URLs in the batch (after de-duplication).
succeededintegerItems captured successfully so far.
failedintegerItems that failed so far.
zipKeystring | nullInternal storage key of the ZIP archive; null until packaging finishes or when every URL failed.
zipBytesinteger | nullZIP archive size in bytes; null until packaged.
deliveryAttemptsintegerWebhook delivery attempts made so far (up to 4).
statusCodeinteger | nullLast webhook HTTP status; null for poll-only jobs or network-level failures.
errorstring | nullFailure reason (packaging or delivery); null otherwise.
startedAtstring | nullISO 8601 timestamp when rendering started.
finishedAtstring | nullISO 8601 terminal timestamp.
createdAtstringISO 8601 creation timestamp.
updatedAtstringISO 8601 last-update timestamp.

Each entry in items:

FieldTypeDescription
idstringItem ID (UUID).
indexinteger1-based position in the submitted URL list (also used for ZIP file naming).
urlstringThe captured URL.
statusstringpending → running → succeeded | failed.
contentTypestring | nullMIME type of the capture; null until succeeded.
bytesinteger | nullCapture size in bytes; null until succeeded.
durationMsinteger | nullCapture time in milliseconds; null until finished.
creditsUsedintegerCredits charged for this item (0 while pending or on failure).
errorstring | nullFailure 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:

FieldTypeDescription
eventstringAlways batch.completed.
batchIdstringThe batch job’s ID (matches X-SnapAPI-Delivery).
namestringJob label.
totalintegerTotal URL count.
succeededintegerSuccessful captures.
failedintegerFailed URLs.
zipUrlstring | nullSigned link to the ZIP archive, valid for 7 days without an API key; null when every URL failed (no archive produced).
zipBytesinteger | nullZIP archive size in bytes; null when no archive was produced.
createdAtstringISO 8601 job creation timestamp.
finishedAtstring | nullISO 8601 packaging completion timestamp.
timestampstringISO 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.ext with the 1-based submission index (for example 001-example.com.png), so file order matches your input order.
  • The zipUrl link 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 currentImageUrl to another image URL. Both images are downloaded and compared; this costs one diff credit.
  • Image vs live page — set url instead. 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-data with baseline and/or current file fields. Uploads mix freely with URLs and live captures (e.g. an uploaded baseline plus url); this costs one diff credit, plus one screenshot credit when using url.

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
  }'
ParameterTypeDefaultDescription
baselineImageUrlstringrequired*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.
baselinefile—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).
currentImageUrlstring—URL of the current image to compare. Provide exactly one of currentImageUrl, current, or url.
currentfile—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).
urlstring—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.
thresholdnumber0.01Changed-pixel ratio (0–1) at or above which hasChanged is true. 0.01 means 1% of pixels differ.
produceDiffImagebooleanfalseWhen 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
}
FieldTypeDescription
okbooleanAlways true on success (false on errors).
thresholdnumberThe threshold used for the comparison (0–1).
changePercentagenumberRatio of changed pixels to total pixels (0–1).
changePercentnumberChanged pixels as a percentage, rounded to two decimals (e.g. 1.25).
hasChangedbooleantrue when changePercentage meets or exceeds threshold.
diffPixelsintegerNumber of pixels that differ between the two images.
totalPixelsintegerTotal pixels compared (width × height after normalization).
width / heightintegerDimensions of the normalized images that were compared.
diffImageobject | nullPresent 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 / currentobjectMetadata for each image: { source: "url" | "upload" | "capture", bytes, contentType }. "upload" marks a multipart file, "capture" a live page screenshot (current only, also includes durationMs).
creditsUsedintegerCredits 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

MethodPathDescription
GET/api/v1/monitorsList all monitors owned by the current API key. The webhookSecret field is omitted.
POST/api/v1/monitorsCreate 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}/triggerRun the comparison immediately. Returns 202 with a runId; the run executes in the background.
GET/api/v1/monitors/{id}/runsList 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 }
}
ParameterTypeDefaultDescription
urlstringrequiredThe page URL to capture and compare on every run. Must be a valid http(s) URL.
cronstringrequiredStandard 5-field cron expression in UTC, e.g. "0 */6 * * *" (every 6 hours). Minimum granularity is one minute.
webhookUrlstring—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.
namestring—Monitor name, up to 64 characters.
optionsobject{}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.
baselineModestring"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).
thresholdnumber0.01Changed-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:

FieldTypeDescription
idstringMonitor ID (UUID).
namestringMonitor label.
urlstringThe page captured and compared on every run.
cronstring5-field cron expression in UTC.
webhookUrlstring | nullAlert destination, or null when alerts are dashboard-only.
webhookSecretstring | nullPer-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.
optionsobjectScreenshot options applied to every run.
baselineModestringfixed, auto, or manual — see the create parameters.
thresholdnumberChanged-pixel ratio that triggers an alert.
statusstringactive or paused.
pauseReasonstring | nullWhy the monitor is paused: user_paused, insufficient_credits, consecutive_failures, invalid_cron, or plan_required; null while active.
baselineUpdatedAtstring | nullISO 8601 timestamp of the current baseline; null until the first run establishes one.
nextRunAtstring | nullISO 8601 timestamp of the next scheduled comparison (UTC); null while paused.
lastRunAtstring | nullISO 8601 timestamp of the most recent run.
consecutiveFailuresintegerConsecutive failed runs; the monitor auto-pauses when this reaches 5.
createdAt / updatedAtstringISO 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:

FieldTypeDescription
idstringRun ID (also sent as the X-SnapAPI-Delivery header on alerts).
statusstringrunning → delivering → delivered | failed. The first run (and any run that rebuilt a missing baseline) establishes the baseline and never alerts.
triggerstringscheduled (cron) or manual (trigger endpoint).
changePercentagenumber | nullChanged-pixel ratio (0–1); null on the baseline-setting run or on failure.
hasChangedboolean | nullWhether the change met the threshold; null on the baseline run or on failure.
thresholdnumber | nullAlert threshold in effect for the run.
baselineUpdatedbooleantrue when this run established or rolled the baseline (first run, auto mode, or a rebuilt baseline).
bytesinteger | nullCapture size in bytes; null when the capture failed.
durationMsinteger | nullCapture-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).
creditsUsedintegerCredits charged (one screenshot + one diff); 0 when the capture failed.
deliveryAttemptsintegerRetries scheduled after failed deliveries (0–3): the initial delivery plus up to three retries, for at most four attempts total.
statusCodeinteger | nullLast webhook HTTP status; null for dashboard-only monitors or network-level failures.
errorstring | nullFailure cause (capture, storage, compare, or delivery); null after success.
firedAt / finishedAtstringISO 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:

FieldTypeDescription
eventstringmonitor.changed (a change met the threshold) or monitor.failed (the run errored).
taskIdstringThe monitor’s ID.
runIdstringThe run’s ID (matches X-SnapAPI-Delivery).
urlstringThe page URL that was captured.
baselineUrlstring | nullSigned 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.
currentUrlstring | nullSigned link to the current capture, valid for 7 days; null on failure.
diffUrlstring | nullSigned 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.
changePercentagenumber | nullChanged-pixel ratio (0–1); null on failure.
changePercentnumber | nullChanged pixels as a percentage, rounded to two decimals; null on failure.
hasChangedboolean | nullWhether the change met the threshold; null on failure.
thresholdnumberAlert threshold in effect for the monitor — present on both changed and failed events.
baselineUpdatedbooleantrue when this run rolled or rebuilt the baseline.
durationMsinteger | nullCapture-and-compare time in milliseconds; failed events still report the elapsed attempt time, and it is null only for un-timed internal crashes.
errorstring | nullFailure cause for monitor.failed; null on changed events.
timestampstringISO 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 reports storage: "s3" with the bucket and key (plus region, endpoint, provider) so you can locate the object; the platform-signed url is null in this case. Objects are stored under <keyPrefix>/<yyyy>/<mm>/<dd>/<uuid>.<ext> — snapapi/… by default (the prefix is customizable in the dashboard), with scheduled/ and batch/ sub-prefixes for those deliveries.
  • Scheduled screenshots and batch ZIP archives are uploaded directly to your bucket, and the screenshotUrl / zipUrl in 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" }
FieldTypeDescription
okbooleanAlways false on errors.
errorstringHuman-readable error message.
detailsobjectPresent only on 400 validation errors: Zod flatten() output (formErrors / fieldErrors) describing exactly which parameter was rejected.
codestringPresent only on 422 errors: a machine-readable failure code — blocked_url, target_unreachable, rendering_failed, blank_capture, image_decode_failed, or storage_unavailable.
StatusMeaning
400Invalid request body — a parameter is missing or has an invalid value.
401Missing or invalid API key — the Authorization header is absent or the key is invalid / revoked.
402Insufficient credits — your account is out of credits. Upgrade a plan or wait for the monthly reset.
403Plan 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.
413Request body too large — the JSON body exceeds 1 MB, or an uploaded diff image exceeds the 25 MiB per-file limit.
422Target 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).
429Rate limit exceeded — too many requests per second. The Retry-After header indicates when to retry.
503Render 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 screenshot

Python (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

ToolDescription
take_screenshotCapture 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_contentExtract 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_metadataRead the page’s title, description, and other metadata. Consumes one credit per call. Requires the Starter or Pro plan.
diff_pagesCapture 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_infoCheck 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.

SnapAPI — Screenshots for AI Agents