Glanceshot
Developer Docs

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

  1. Sign up for free, then create a key on the API Key page in the console.
  2. Pass your key in the X-Access-Key header 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.png

Authentication

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

GET/v1/screenshot
POST/v1/screenshot

Screenshot-only parameters:

ParameterTypeDefaultDescription
formatenumpngpng, jpeg, or webp.
device_scale_factornumber1Scale factor, 1–3. Set to 2 for Retina resolution, with roughly 4 times the file size.
qualityinteger80JPEG / WebP quality, 1–100.
full_pagebooleanfalseCapture the full page. Scrolls through the page to trigger lazy loading, up to a height of 20,000 pixels.
selectorstring—Capture only the first element matching this CSS selector. Cannot be used with full_page.

Parameters shared by screenshots and PDFs:

ParameterTypeDefaultDescription
urlstring—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).
htmlstring—Provide HTML directly (up to 2 MB). POST only.
viewport_widthinteger1280Viewport width, 320–3840. Values below 768 use mobile rendering.
viewport_heightinteger800Viewport height, 200–3840.
wait_untilenumnetworkidleWait 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_selectorstring—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.
delayinteger0Additional delay in milliseconds after waiting, 0–10000. Useful for animations or lazy-loaded components.
block_adsbooleantrueBlock requests to ad and tracking domains and hide ad placements on the page.
block_cookie_bannersbooleantrueHide cookie consent banners.
dark_modebooleanfalseEmulate prefers-color-scheme: dark.
hide_selectorsstring—A list of CSS selectors for elements to hide, such as .ad,#popup.
cachebooleantrueWhether to check the cache. Set to false to force a new render. The result is still cached.
cache_ttlinteger86400Cache lifetime in seconds, 60–2592000.
response_typeenumbinarybinary returns the file directly; json returns the file URL and metadata.
proxystring—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.
cookiesstring | 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.
headersobject—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_storageobject—localStorage entries to set for the target URL's origin. Keys and values must be strings.
session_storageobject—Same as local_storage, but writes to sessionStorage.
login_profilestring—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

GET/v1/pdf
POST/v1/pdf

Each PDF uses 2 credits. Before printing, Glanceshot scrolls through the page to trigger lazy-loaded content. In addition to the shared parameters, PDFs support:

ParameterTypeDefaultDescription
paperenuma4Paper size: a4 or letter. The page's own @page rules take precedence.
print_backgroundbooleantruePrint background colors and images.
pdf_mediaenumprintprint uses print styles; screen uses screen styles (what you see is what you get).
HTML to PDF
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.pdf

Responses

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: HIT or MISS
  • X-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:

JSON Response
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:

Signing Rules
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.png
  • proxy is 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_failed and is not billed. message provides 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 (GET and 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_expired at 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 Format
{
  "error": {
    "code": "render_failed",
    "message": "target responded with an error status",
    "upstream_status": 404,
    "request_id": "req_01j…"
  }
}
HTTPcodeMeaningBilled
400invalid_paramsA parameter is missing or out of range; message identifies the parameterNo
400html_too_largehtml exceeds 2 MBNo
401unauthorizedAPI Key is missing, invalid, or disabledNo
401signature_invalidSignature mismatch (parameters were changed or the secret is incorrect)No
401signature_expiredThe signed URL has passed its expires timeNo
403url_blockedAccess to the target address is not allowed (for example, private network addresses or localhost)No
408render_timeoutRendering exceeded 30 secondsNo
422render_failedThe target site returned 4xx/5xx, DNS failed, or a certificate error occurred; includes upstream_statusNo
422selector_not_foundNo element matched selector, or wait_for_selector timed outNo
422too_many_redirectsMore than 5 redirectsNo
422site_blockedThe target website showed a bot check, CAPTCHA, login wall, or access denied page instead of normal content. You are not billed.No
422login_expiredLogin credentials were provided, but the target website still shows the login page, indicating that the login session has expired.No
422proxy_failedThe 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
429rate_limitedRequest rate limit exceeded; retry according to Retry-AfterNo
429concurrency_exceededConcurrency limit exceededNo
429quota_exceededMonthly credits used up (free plan)No
429too_many_batchesMore than 5 unfinished batch jobsNo
500internalInternal server error. Contact us with request_idNo

Async Batches

POST/v1/batch
GET/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).

ParameterTypeDefaultDescription
typeenumscreenshotscreenshot (screenshot) or pdf.
urlsstring[]—URLs to process, 1–100. If any URL is invalid, the entire request returns 400, and message identifies its position in the list.
optionsobject{}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_urlstring—Optional. URL to notify when all items are complete. See "Webhooks."
Submit a Batch Job
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).

Check Batch Status
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 with quota_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.url includes 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…: t is the send time (Unix seconds), and v1 is the signature
  • X-Glanceshot-Event: batch.completed, X-Glanceshot-Delivery-Attempt: 1 (delivery attempt number)
Signing Rules
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 t is 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

GET/v1/usage
shell
curl "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

GET/v1/health

No authentication required. Returns the node status and browser pool counts (total, busy, and idle).