API Documentation
Base URL: https://glanceshot.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://glanceshot.com/v1/screenshot?url=https://example.com&full_page=true" \
-H "X-Access-Key: $GLANCESHOT_KEY" \
-o screenshot.pngAuthentication
Every request requires an API Key. Pass it using any of these methods:
- Header:
X-Access-Key: gls_…(recommended for server-side requests) - Query parameter
access_key=gls_…(server-side requests only; never include it in a web page) - Signed URL (use when embedding the URL in a web page; neither your API Key nor your secret is exposed)
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 and hide ad placements on the page. |
| 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. |
| proxy | string | — | Optional. Use your own paid proxy to access the target page, for example, from a specific country or region. You cover any proxy costs. Supports socks5:// and http://, with optional username and password authentication, in the format socks5://user:pass@203.0.113.10:1080. Only supported in the POST request body, not in GET requests, signed URLs, or batch jobs. Requests using a proxy are cached separately. If the proxy connection fails, authentication fails, or the proxy cannot reach the target site, the request returns proxy_failed and is not billed. |
| cookies | string | object[] | — | Cookies for the target website, either as a single line like sid=abc; theme=dark or an array like [{"name":"sid","value":"abc"}] (with optional domain and path). Sent only to the target website, not to third parties on the page. |
| headers | object | — | Additional request headers, such as Authorization: Bearer …. Sent only to the target website. Browser-managed headers (Host, Cookie, User-Agent, etc.) cannot be set. |
| local_storage | object | — | localStorage entries to set for the target URL's origin. Keys and values must be strings. |
| session_storage | object | — | Same as local_storage, but writes to sessionStorage. |
| login_profile | string | — | The ID of a login profile saved with the browser extension (lp_8f2c…). Easier than passing cookies directly, and works with batch jobs too (no credentials are stored in the jobs). |
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, Glanceshot 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://glanceshot.com/v1/pdf" \
-H "X-Access-Key: $GLANCESHOT_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-Glanceshot-Cache:HITorMISSX-Glanceshot-Truncated: true: The full page exceeded 20,000 pixels and was truncated.X-Glanceshot-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://glanceshot.com/v1/screenshot" \
-H "X-Access-Key: $GLANCESHOT_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "response_type": "json"}'
# Returns
{
"url": "https://glanceshot.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? Signed URLs contain only the public key_id, not your API Key. Generate the signature on your server using the secret associated with that key. You can find both the Key ID and secret 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. Requests with missing or invalid signatures are rejected. 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 arbitrary parameter (such as v=2) and sign the URL again. Signed URLs support only GET requests to the screenshot and PDF endpoints and cannot include proxy parameters.
key_id is public and cannot be used on its own to call the API. Never include your API Key (access_key) in a signed URL; requests that include it return invalid_params. If you previously included an API Key in a web page, disable it in the console and create a new one.
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, keyId, secret) {
const all = { ...params, key_id: keyId };
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://glanceshot.com/v1/screenshot?${canonical}&signature=${signature}`;
}
const src = signedUrl({ url: "https://example.com", viewport_width: 1200 }, KEY_ID, SECRET);
// <img src={src} alt="Web page preview" />Using a Proxy
To access a page from a specific country or region (for example, to see local languages, content, or prices), include your own paid proxy in the request. You are responsible for proxy costs. The page and all its images, scripts, and API requests load through the proxy. Both socks5:// and http:// proxies are supported, with optional username and password authentication.
curl -X POST "https://glanceshot.com/v1/screenshot" \
-H "X-Access-Key: $GLANCESHOT_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "proxy": "socks5://user:pass@203.0.113.10:1080"}' \
-o screenshot.pngproxyis only allowed in the POST request body. It cannot be used with GET requests, signed URLs, or batch jobs, keeping proxy credentials out of URLs, browser history, and web pages.- If your username or password contains special characters such as
@or:, URL-encode them first (for example, write@as%40). - Requests that use a proxy are cached separately. They do not share cached results with requests that use no proxy or a different proxy.
- If the proxy connection fails, authentication fails, or the proxy cannot reach the target website, the request returns
proxy_failedand is not billed.messageprovides the specific reason. - The proxy must have a public address. The same target URL restrictions apply with or without a proxy: private network addresses are not allowed.
- When accessing websites through a proxy, comply with the target website's terms of service and local laws.
Capture authenticated pages
To capture pages that require authentication, provide your own account's login credentials. There are two options: pass cookies, headers, local_storage, and session_storage directly in the request, or use the Glanceshot browser extension to save a "login profile" and include only login_profile in the request.
curl -X POST "https://glanceshot.com/v1/screenshot" -H "X-Access-Key: $GLANCESHOT_KEY" -H "Content-Type: application/json" -d '{
"url": "https://app.example.com/dashboard",
"cookies": "sid=YOUR_SESSION; theme=dark",
"local_storage": {"token": "YOUR_TOKEN"}
}' -o screenshot.png- The extension can read login credentials that page scripts cannot access (including HttpOnly cookies), as well as localStorage and IndexedDB. It works with most websites. Install and connect the extension from the "Login Profiles" page in the console.
- Login credentials can only be sent in a POST request body (
GETand signed URLs are rejected). They are never written to logs or request history. Requests with login credentials are cached separately based on those credentials and are never shared with other users. - If login credentials are provided but the page redirects to the login page, the API returns
login_expiredat no charge. This means the login session has expired; sync it again with the extension. - For websites with sessions tied to a device or IP that cannot be used in the cloud (such as online banking), use the extension's "Capture Current Page" feature to take a screenshot in your own browser and upload it.
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, rendering failures, target site errors or access blocks, invalid parameters, and authentication failures do not count against your quota.
- 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 |
| 422 | site_blocked | The target website showed a bot check, CAPTCHA, login wall, or access denied page instead of normal content. You are not billed. | No |
| 422 | login_expired | Login credentials were provided, but the target website still shows the login page, indicating that the login session has expired. | No |
| 422 | proxy_failed | The proxy you provided could not connect, failed authentication, or could not reach the target site. See message for details. The request is not billed. | 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. Accepts the same parameters as the synchronous API (such as full_page, format, viewport_width, and cache_ttl), except url, html, response_type, and proxy. |
| webhook_url | string | — | Optional. URL to notify when all items are complete. See "Webhooks." |
curl -X POST "https://glanceshot.com/v1/batch" \
-H "X-Access-Key: $GLANCESHOT_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/glanceshot"
}'
# 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/glanceshot", "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://glanceshot.com/v1/batch/bat_01j…" -H "X-Access-Key: $GLANCESHOT_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://glanceshot.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-Glanceshot-Signature: t=1760779812,v1=5d3f…:tis the send time (Unix seconds), andv1is the signatureX-Glanceshot-Event: batch.completed,X-Glanceshot-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 verifyGlanceshotWebhook(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);
}
// verifyGlanceshotWebhook(req.rawBody, req.headers["x-glanceshot-signature"], GLANCESHOT_SECRET)Usage
/v1/usagecurl "https://glanceshot.com/v1/usage" -H "X-Access-Key: $GLANCESHOT_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).