API Documentation
Base URL: https://yhfdghkh6544.com/v1. All endpoints use standard HTTP. Pass parameters in the query string (GET) or JSON body (POST).
Quickstart
- Sign up for free, then create a key on the API Key page in the console.
- Pass your key in the
X-Access-Keyheader and call the screenshot endpoint:
curl "https://yhfdghkh6544.com/v1/screenshot?url=https://example.com&full_page=true" \
-H "X-Access-Key: $SNAPI_KEY" \
-o screenshot.pngAuthentication
Every request requires an API Key. Pass it using any of these methods:
- Header:
X-Access-Key: snp_…(recommended for server-side requests) - Query parameter:
access_key=snp_… - Signed URL (for embedding URLs in web pages without exposing your secret)
Your API Key is shown in full only once, when you create it. Store it securely. If it is exposed, disable it in the console.
Screenshot API
/v1/screenshot/v1/screenshotScreenshot-only parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| format | enum | png | png, jpeg, or webp. |
| device_scale_factor | number | 1 | Scale factor, 1–3. Set to 2 for Retina resolution, with roughly 4 times the file size. |
| quality | integer | 80 | JPEG / WebP quality, 1–100. |
| full_page | boolean | false | Capture the full page. Scrolls through the page to trigger lazy loading, up to a height of 20,000 pixels. |
| selector | string | — | Capture only the first element matching this CSS selector. Cannot be used with full_page. |
Parameters shared by screenshots and PDFs:
| Parameter | Type | Default | Description |
|---|---|---|---|
| url | string | — | The page URL to capture, using http or https. Provide either this or html. The # fragment is preserved, so you can capture specific routes in hash-based single-page apps (such as https://app.com/#/report/1). |
| html | string | — | Provide HTML directly (up to 2 MB). POST only. |
| viewport_width | integer | 1280 | Viewport width, 320–3840. Values below 768 use mobile rendering. |
| viewport_height | integer | 800 | Viewport height, 200–3840. |
| wait_until | enum | networkidle | Wait strategy: domcontentloaded (fastest, best for static pages), load, or networkidle (tracks all requests from navigation and captures after page load once no requests have been in flight for 500 milliseconds; waits up to 8 additional seconds, best for single-page apps). |
| wait_for_selector | string | — | After page load, wait up to 10 seconds for an element matching this CSS selector to appear and become visible before capturing. Returns selector_not_found if it times out (not billed). Useful for single-page apps that render content using timers or animations rather than network requests, such as .chart-ready. |
| delay | integer | 0 | Additional delay in milliseconds after waiting, 0–10000. Useful for animations or lazy-loaded components. |
| block_ads | boolean | true | Block requests to ad and tracking domains. |
| block_cookie_banners | boolean | true | Hide cookie consent banners. |
| dark_mode | boolean | false | Emulate prefers-color-scheme: dark. |
| hide_selectors | string | — | A list of CSS selectors for elements to hide, such as .ad,#popup. |
| cache | boolean | true | Whether to check the cache. Set to false to force a new render. The result is still cached. |
| cache_ttl | integer | 86400 | Cache lifetime in seconds, 60–2592000. |
| response_type | enum | binary | binary returns the file directly; json returns the file URL and metadata. |
Capturing single-page apps (React, Vue, etc.):Pages run JavaScript in a real browser before capture. The default networkidle waits for the page to finish fetching data. If content renders later using timers or animations, add wait_for_selector to specify an element that indicates the content is ready. If no suitable element exists, use delay to wait a little longer.
PDF API
/v1/pdf/v1/pdfEach PDF uses 2 credits. Before printing, Snapi scrolls through the page to trigger lazy-loaded content. In addition to the shared parameters, PDFs support:
| Parameter | Type | Default | Description |
|---|---|---|---|
| paper | enum | a4 | Paper size: a4 or letter. The page's own @page rules take precedence. |
| print_background | boolean | true | Print background colors and images. |
| pdf_media | enum | print uses print styles; screen uses screen styles (what you see is what you get). |
curl -X POST "https://yhfdghkh6544.com/v1/pdf" \
-H "X-Access-Key: $SNAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Quarterly Report</h1><p>Body text…</p>", "paper": "a4"}' \
-o report.pdfResponses
By default (response_type=binary), the file is returned directly with the appropriate Content-Type. Response headers:
X-Request-Id: Request ID. Include this when reporting an issue.X-Snapi-Cache:HITorMISSX-Snapi-Truncated: true: The full page exceeded 20,000 pixels and was truncated.X-Snapi-Format-Changed: webp: PNG output exceeded 5 MB and was automatically converted to WebP (not converted when format=png is explicitly specified).
Set response_type=json to return the file URL and metadata:
curl -X POST "https://yhfdghkh6544.com/v1/screenshot" \
-H "X-Access-Key: $SNAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "response_type": "json"}'
# Returns
{
"url": "https://yhfdghkh6544.com/i/3f/3f9c….png",
"cache_hit": false,
"duration_ms": 1284,
"format": "png",
"width": 1280,
"height": 800,
"request_id": "req_01j…"
}Signed URLs
Want to use a screenshot URL directly in <img src> without exposing your API Key? Generate a signature on your server using the key's secret (available on the API Key page in the console):
canonical = Sort all parameters except signature lexicographically by name,
format each as k=urlencode(v) (RFC 3986, with spaces encoded as %20), and join them with &
signature = hex(HMAC-SHA256(secret, canonical))A valid signature authenticates the request. Changing any parameter (such as url) invalidates the signature. The optional expires parameter (Unix seconds) is included in the signature; expired URLs return signature_expired. To refresh the cache, change an otherwise unused parameter (such as v=2) and sign the URL again. Signed URLs support GET only.
import crypto from "node:crypto";
// RFC 3986 encoding (spaces as %20) must match the server's encoding rules
const enc = (v) =>
encodeURIComponent(v).replace(/[!'()*]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase());
function signedUrl(params, accessKey, secret) {
const all = { ...params, access_key: accessKey };
const canonical = Object.keys(all).sort().map((k) => `${k}=${enc(String(all[k]))}`).join("&");
const signature = crypto.createHmac("sha256", secret).update(canonical).digest("hex");
return `https://yhfdghkh6544.com/v1/screenshot?${canonical}&signature=${signature}`;
}
const src = signedUrl({ url: "https://example.com", viewport_width: 1200 }, KEY, SECRET);
// <img src={src} alt="Web page preview" />Caching
Requests with identical parameters share a cache entry (omitting a parameter is equivalent to explicitly passing its default; authentication and cache parameters are excluded from comparison). On a cache hit, binary mode returns a 302 redirect to the file URL with Cache-Control. Browsers follow the redirect and cache the file automatically. JSON mode returns cache_hit: true.
Cache hits are not billed.Concurrent requests with identical parameters trigger only one render.
Limits & Billing
- Only successful (2xx) requests that miss the cache are billed: 1 credit per screenshot, 2 credits per PDF.
- Timeouts, render failures, target site errors, invalid parameters, and authentication failures do not use credits.
- Credits reset each calendar month (UTC). The free plan includes 100 credits per month. Requests return quota_exceeded once they are used up.
- Exceeding the request rate or concurrency limit returns 429. The Retry-After response header specifies the recommended wait time in seconds.
- Maximum render time: 30 seconds. Maximum download per page: 50 MB. Maximum output file size: 50 MB.
For each plan's limits, see Pricing.
Error Codes
All error responses use JSON:
{
"error": {
"code": "render_failed",
"message": "target responded with an error status",
"upstream_status": 404,
"request_id": "req_01j…"
}
}| HTTP | code | Meaning | Billed |
|---|---|---|---|
| 400 | invalid_params | A parameter is missing or out of range; message identifies the parameter | No |
| 400 | html_too_large | html exceeds 2 MB | No |
| 401 | unauthorized | API Key is missing, invalid, or disabled | No |
| 401 | signature_invalid | Signature mismatch (parameters were changed or the secret is incorrect) | No |
| 401 | signature_expired | The signed URL has passed its expires time | No |
| 403 | url_blocked | Access to the target address is not allowed (for example, private network addresses or localhost) | No |
| 408 | render_timeout | Rendering exceeded 30 seconds | No |
| 422 | render_failed | The target site returned 4xx/5xx, DNS failed, or a certificate error occurred; includes upstream_status | No |
| 422 | selector_not_found | No element matched selector, or wait_for_selector timed out | No |
| 422 | too_many_redirects | More than 5 redirects | No |
| 429 | rate_limited | Request rate limit exceeded; retry according to Retry-After | No |
| 429 | concurrency_exceeded | Concurrency limit exceeded | No |
| 429 | quota_exceeded | Monthly credits used up (free plan) | No |
| 429 | too_many_batches | More than 5 unfinished batch jobs | No |
| 500 | internal | Internal server error. Contact us with request_id | No |
Async Batches
/v1/batch/v1/batch/{id}Submit 1–100 URLs and receive a job ID immediately (HTTP 202). Screenshots are processed one at a time in the background, so you don't need to keep a connection open. Poll the status endpoint or provide webhook_url to be notified when all items are complete. Authentication works like the synchronous endpoints (header or access_key parameter only; signed URLs are not supported).
| Parameter | Type | Default | Description |
|---|---|---|---|
| type | enum | screenshot | screenshot (screenshot) or pdf. |
| urls | string[] | — | URLs to process, 1–100. If any URL is invalid, the entire request returns 400, and message identifies its position in the list. |
| options | object | {} | Parameters shared by all URLs. Supports the same parameters as the synchronous endpoints (such as full_page, format, viewport_width, and cache_ttl), except url, html, and response_type. |
| webhook_url | string | — | Optional. URL to notify when all items are complete. See "Webhooks." |
curl -X POST "https://yhfdghkh6544.com/v1/batch" \
-H "X-Access-Key: $SNAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "screenshot",
"urls": ["https://example.com", "https://example.org"],
"options": { "full_page": true, "format": "webp" },
"webhook_url": "https://your-app.com/hooks/snapi"
}'
# Returns 202
{
"id": "bat_01j…",
"type": "screenshot",
"status": "queued",
"total": 2, "succeeded": 0, "failed": 0,
"created_at": "2026-10-18T09:30:00Z", "completed_at": null,
"webhook": { "url": "https://your-app.com/hooks/snapi", "status": "none", "attempts": 0 },
"items": [
{ "index": 0, "url": "https://example.com", "status": "pending", "result": null, "error": null },
{ "index": 1, "url": "https://example.org", "status": "pending", "result": null, "error": null }
]
}Check progress: The job's status moves from queued (queued) to running (processing) to completed (all items finished, whether successful or failed). Each URL has a status of pending, running, succeeded, or failed. Successful items include the file URL in result; failed items include an error code in error (the same codes as the synchronous endpoints).
curl "https://yhfdghkh6544.com/v1/batch/bat_01j…" -H "X-Access-Key: $SNAPI_KEY"
{
"id": "bat_01j…",
"status": "completed",
"total": 2, "succeeded": 1, "failed": 1,
"completed_at": "2026-10-18T09:30:12Z",
"items": [
{
"index": 0, "url": "https://example.com", "status": "succeeded", "duration_ms": 1830,
"result": { "url": "https://yhfdghkh6544.com/i/3f/3f9c….webp", "mime": "image/webp", "width": 1280, "height": 2410, "cache_hit": false },
"error": null
},
{
"index": 1, "url": "https://example.org", "status": "failed", "duration_ms": 640,
"result": null,
"error": { "code": "render_failed", "message": "target responded with an error status" }
}
]
}- Billing works like the synchronous endpoints: each successful URL uses 1 credit for a screenshot or 2 credits for a PDF. Cache hits and failures do not use credits.
- If you have no credits left when submitting, the request immediately returns
quota_exceeded. If credits run out during processing, the remaining URLs fail withquota_exceeded. - Each account can have up to 5 unfinished batch jobs at a time. Exceeding this limit returns
too_many_batches. - Result files expire according to
options.cache_ttl(default: 1 day). Download and save them before they expire. - In status responses and webhook payloads,
webhook.urlincludes only the URL through its path; the query string is omitted.
Webhooks
If you provide webhook_url, we send a POST to it when all items are complete. The JSON request body is { "event": "batch.completed", "batch": { … } }, where batch is identical to the status endpoint's response. Headers:
X-Snapi-Signature: t=1760779812,v1=5d3f…:tis the send time (Unix seconds), andv1is the signatureX-Snapi-Event: batch.completed,X-Snapi-Delivery-Attempt: 1(delivery attempt number)
v1 = hex(HMAC-SHA256(secret, t + "." + Raw request body))
secret is the signing secret for the API Key used to submit the job (available on the API Key page in the console).- Return a 2xx response within 10 seconds, or the delivery is considered failed. Failed deliveries are retried after 10 seconds, 1 minute, and 5 minutes, for a maximum of 4 attempts.
- In rare cases, you may receive duplicate notifications for the same job. Deduplicate by
batch.id. - To verify the signature, compute it using the raw bytes received and check that
tis within a few minutes of the current time to prevent replay attacks. - The callback URL must be a publicly accessible http(s) address and cannot point to a private network.
import crypto from "node:crypto";
// rawBody must contain the original bytes received. Do not use JSON.parse and then reserialize.
function verifySnapiWebhook(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // Replay protection
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const got = Buffer.from(parts.v1 ?? "");
return got.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), got);
}
// verifySnapiWebhook(req.rawBody, req.headers["x-snapi-signature"], SNAPI_SECRET)Usage
/v1/usagecurl "https://yhfdghkh6544.com/v1/usage" -H "X-Access-Key: $SNAPI_KEY"
{ "month": "202610", "used": 37, "limit": 100, "remaining": 63, "overage": 0, "resets_at": "2026-11-01T00:00:00Z", ... }Health Check
/v1/healthNo authentication required. Returns the node status and browser pool counts (total, busy, and idle).