HTTP API Reference

This is the public HTTP API for Snap.

Base URL

https://snap.i2dev.com

Authentication

Pass your API key as a bearer token:

http
Authorization: Bearer YOUR_API_KEY

A key is shown in full only once, when you create it in the dashboard; Snap can't show it again. If you lose a key, create a new one and revoke the old one.

POST /v1/screenshots

Captures a screenshot of a URL (synchronous). Returns an imageUrl plus metadata.

Example request (copy/paste)

bash
curl -sS -X POST https://snap.i2dev.com/v1/screenshots   -H "Authorization: Bearer YOUR_API_KEY"   -H "Content-Type: application/json"   -d '{
    "url": "https://example.com",
    "format": "png",
    "fullPage": false,
    "viewport": { "width": 1280, "height": 720 },
    "waitUntil": "load"
  }'

Example response

json
{
  "jobId": "...",
  "request_id": "...",
  "imageUrl": "https://snap-i2dev-com.s3.amazonaws.com/...?X-Amz-...",
  "contentType": "image/png",
  "bytes": 12345,
  "durationMs": 987,
  "logs": [],
  "page_errors": []
}

Result URLs and retention

Use imageUrl to retrieve the finished screenshot. Authenticated result URLs are signed and expire. Synchronous results default to seven days; async results default to one hour. Both are deployment-configurable. Download or copy an artifact that must outlive its URL. logs and page_errors contain capture diagnostics and may be empty.

Async mode, when enabled

Set "async": true in the POST /v1/screenshots body to queue the render and return immediately. Then poll GET /v1/jobs/:id for status. If async mode is unavailable on the deployment, the API returns a 400 bad_request; synchronous capture remains available.

Async submit example

bash
curl -sS -X POST https://snap.i2dev.com/v1/screenshots   -H "Authorization: Bearer YOUR_API_KEY"   -H "Content-Type: application/json"   -d '{
    "url": "https://example.com",
    "format": "png",
    "async": true
  }'

Async submit response

json
{
  "jobId": "...",
  "request_id": "...",
  "status": "queued",
  "statusUrl": "https://snap.i2dev.com/v1/jobs/..."
}

Poll job status

bash
curl -sS -H "Authorization: Bearer YOUR_API_KEY"   https://snap.i2dev.com/v1/jobs/JOB_ID

Job status response (done)

json
{
  "jobId": "...",
  "status": "succeeded",
  "result": {
    "imageUrl": "https://snap-i2dev-com.s3.amazonaws.com/...?X-Amz-...",
    "contentType": "image/png",
    "bytes": 12345,
    "durationMs": 987
  }
}

Batch: many URLs in one request

Send an items array instead of a single url to capture up to 50 pages in one request. Options at the top level apply to every item, and each item may override any of them, field by field — an item that sets only viewport.width keeps the shared viewport.height. Every item needs its own url. A batch is always asynchronous: it returns a batchId immediately, which you poll at GET /v1/screenshots/:batchId. Because it is asynchronous, it needs the same deployment switch as async mode and returns 400 bad_request where that is unavailable.

Each item costs one render from your monthly allowance, charged up front and all-or-nothing: a batch larger than your remaining allowance is refused whole with 429 quota_exceeded and costs you nothing. A batch containing a URL we will not fetch — a private address, a non-HTTP scheme — is likewise rejected before anything is charged, with the offending index in the error details.

A URL that simply does not resolve is treated differently: it is accepted and fails as its own item, so one dead link in a sitemap does not throw away the rest of the batch. Items render independently, so a batch can finish with a mix of succeeded and failed items — check counts rather than assuming completed means every item worked.

Batch submit example

bash
curl -sS -X POST https://snap.i2dev.com/v1/screenshots   -H "Authorization: Bearer YOUR_API_KEY"   -H "Content-Type: application/json"   -d '{
    "format": "jpeg",
    "viewport": { "width": 1280, "height": 720 },
    "items": [
      { "url": "https://example.com/" },
      { "url": "https://example.com/pricing", "fullPage": true },
      { "url": "https://example.com/docs", "viewport": { "width": 390, "height": 844 } }
    ]
  }'

Batch submit response

json
{
  "batchId": "...",
  "request_id": "...",
  "count": 3,
  "status": "queued",
  "statusUrl": "https://snap.i2dev.com/v1/screenshots/..."
}

Poll batch status

bash
curl -sS -H "Authorization: Bearer YOUR_API_KEY"   https://snap.i2dev.com/v1/screenshots/BATCH_ID

Batch status response

status is running until every item has finished, then completed — and it stays there. Items come back in the order you submitted them. Results expire (see retention above), so a batch you poll much later can be completed with fewer entries in items than itemCount; fetch what you need before then.

json
{
  "batchId": "...",
  "status": "completed",
  "itemCount": 3,
  "counts": { "queued": 0, "running": 0, "succeeded": 2, "failed": 1 },
  "items": [
    {
      "jobId": "...",
      "url": "https://example.com/",
      "status": "succeeded",
      "imageUrl": "https://snap-i2dev-com.s3.amazonaws.com/...?X-Amz-...",
      "contentType": "image/jpeg",
      "bytes": 12345,
      "durationMs": 987
    },
    {
      "jobId": "...",
      "url": "https://example.com/pricing",
      "status": "failed",
      "error": { "error": "Navigation timeout", "code": "nav_timeout" }
    }
  ]
}

Request body

FieldTypeRequiredDescription
urlstringYesThe URL to screenshot (public http/https only; SSRF protections enabled).
format"png" | "jpeg" | "webp"NoDefault: png. The anonymous GET endpoint defaults to webp instead.
fullPagebooleanNoCapture full scrollable page. Default: false. Not supported for PDF targets: Chromium renders a PDF inside a fixed-size viewer, so a full-page capture would return only the visible portion. Request the same URL with fullPage: false.
viewport{ width, height }NoDefault: {"width":1280,"height":720}
waitUntil"load" | "domcontentloaded" | "networkidle"NoDefault: load. After this condition fires, the renderer always runs a best-effort settle pass (network quiescence up to 5s, font loading, brief animation delay) before capturing, so SPAs and lazy content typically render fully on the default setting.
waitForSelectorstringNoWait for a CSS selector before capturing.
timeoutMsnumberNoOverall render timeout (bounded by server max).
navTimeoutMsnumberNoNavigation timeout (bounded by server max).
timezoneIdstringNoExample: America/Toronto
localestringNoExample: en-US
extraHTTPHeadersobjectNoExtra request headers (string → string).
cookiesarrayNoCookies to set before navigation.
baselineTagstringNoOptional label included in the result's storage identity. It does not publish a Visual CI baseline.
compareWithTagstringNoOptional label included in the result's storage identity. It does not retrieve or diff a previous capture.
asyncbooleanNoQueue render and return immediately (requires async mode enabled).
itemsarrayNoCapture many URLs in one request (max 50). Replaces url; every other field becomes a shared default that each item may override. See Batch.

timezoneId and locale are checked by Chromium when rendering starts, rather than against a server-side allowlist. Values Chromium cannot use return400 invalid_render_option for synchronous requests. Async submissions still return 202, then expose invalid_render_option on the failed job. Unconventional values that Chromium supports remain accepted.

Capturing authenticated pages safely

Supply headers and cookies from CI or repository secrets—never commit authorization values. Cookies must include the target domain. Use the least privilege and shortest lifetime possible, and avoid reusing long-lived personal sessions. Snap forwards these credentials while loading the target page. Header and cookie values are not part of the result's storage identity, so do not use one API key with identical URL/render options for multiple user sessions; a later capture can replace the earlier object.

Rate limits and errors

Errors are returned as JSON and include a request_id. Include that ID when reporting issues.

Authenticated rate-limit responses use HTTP 429 with rate_limited, a Retry-After header, and X-Snap-Rate-Limit-Scope set to ip or api_key. The JSON details object repeats the delay as retry_after_seconds and the scope as scope. too_many_inflight is also a 429: it means the key already holds its concurrency budget, and it carries the same Retry-After header and retry_after_seconds detail with a fixed backoff hint (2 seconds — the actual wait until a slot frees depends on the in-flight renders).

json
{
  "error": "Invalid or missing API key",
  "code": "unauthorized",
  "request_id": "..."
}

Common HTTP status codes:

  • 400 bad_request / invalid_render_option / unsafe_url — invalid_render_option also covers fullPage on a PDF target
  • 401 unauthorized
  • 403 unauthorized_scope — the key does not have the screenshots:write scope. Visual CI keys hold only visual:* scopes and are limited to /v1/visual/*; create a screenshot key on the dashboard keys page for this API.
  • 413 payload_too_large
  • 415 unsupported_media_type — send Content-Type: application/json. Worth checking first if a hand-written request fails immediately: some shells drop the header, and curl removes a header given an empty value.
  • 429 rate_limited / too_many_inflight / quota_exceeded
  • 422 render_blocked — the site served an anti-bot challenge; download_not_supported — the URL returns a file download rather than a page
  • 500 internal_server_error
  • 502 automation_refused / upstream_unreachable — the target site failed, not Snap
  • 503 auth_unavailable — Snap could not verify your API key right now. Not a problem with the key: retry after the Retry-After delay.