Developers

API reference

Two endpoints cover the whole capture flow. All examples use placeholders — YOUR_API_KEY, CAPTURE_ID, and example.com — and match the live request schema.

POST /api/captures

Create a capture. Requires an Authorization: Bearer header with a workspace API key and a JSON body. The target hostname must match one of your active allowed domains.

request — create a capture
curl -X POST "https://sessionshot.com/api/captures" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/dashboard",
    "output": {
      "format": "png",
      "fullPage": true,
      "width": 1280,
      "height": 800
    },
    "auth": {
      "cookies": [
        { "name": "session", "value": "PLACEHOLDER", "domain": "example.com" }
      ],
      "localStorage": [
        { "key": "workspace_id", "value": "acme" }
      ]
    },
    "actions": [
      { "type": "waitForSelector", "selector": "[data-ready]" },
      { "type": "click", "selector": "#reports-tab" }
    ],
    "redaction": {
      "selectors": [".billing-info", "[data-private]"],
      "mode": "block"
    }
  }'
response — 202 Accepted
HTTP/1.1 202 Accepted

{
  "id": "cap_9c1e6c1a-...",
  "status": "queued",
  "createdAt": "2026-07-02T10:15:00.000Z",
  "statusUrl": "/api/captures/cap_9c1e6c1a-..."
}

Request body fields

FieldTypeNotes
urlstring, requiredTarget page. Must be HTTPS.
output.format"png" | "jpeg" | "pdf", default "png"Output type.
output.fullPageboolean, default falseCapture the full scroll height instead of the viewport.
output.width / output.heightint, defaults 1280 × 800Viewport size. Width 320–3840, height 240–4320.
output.qualityint 1–100, optionalJPEG only.
auth.cookiesarray, max 50, optionalSession cookies to inject (name, value, domain, …).
auth.localStoragearray of { key, value }, max 50, optionallocalStorage entries set before navigation.
auth.headersobject, max 20 keys, optionalExtra request headers. Sensitive header names (like authorization or cookie) are blocked — use the dedicated fields.
actionsarray, max 20, optionalOrdered browser steps: waitForSelector { selector, timeout ≤ 15000 }, click { selector }, type { selector, text ≤ 1000 }, scroll { y ≤ 50000 }, wait { ms ≤ 10000 }.
redactionobject, optionalselectors (max 50) masked before rendering; mode is "block" (default) or "blur".
metadataobject, optionalYour own JSON metadata, max 1 KB serialized. Echoed back on creation.

GET /api/captures/:id

Read a capture's status with the same API key that created it. Completed captures include a short-lived signed result URL; failed captures include a safe error code and message.

request — check status
curl "https://sessionshot.com/api/captures/CAPTURE_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"
response — completed
HTTP/1.1 200 OK

{
  "id": "cap_9c1e6c1a-...",
  "status": "completed",
  "createdAt": "2026-07-02T10:15:00.000Z",
  "updatedAt": "2026-07-02T10:15:09.000Z",
  "completedAt": "2026-07-02T10:15:09.000Z",
  "result": {
    "url": "https://...signed-url...",
    "expiresAt": "2026-07-02T10:25:09.000Z",
    "format": "png",
    "bytes": 482113,
    "width": 1280,
    "height": 4820
  }
}
response — failed
HTTP/1.1 200 OK

{
  "id": "cap_9c1e6c1a-...",
  "status": "failed",
  "createdAt": "2026-07-02T10:15:00.000Z",
  "updatedAt": "2026-07-02T10:15:20.000Z",
  "error": {
    "code": "NAVIGATION_TIMEOUT",
    "message": "Target page did not become ready within the time limit"
  }
}

Signed result URLs are short-lived

The result URL expires (see expiresAt, roughly ten minutes). Request the status endpoint again to mint a fresh link — the stored asset is unaffected. Storage is never public.

Polling example

Captures are asynchronous — poll until the status is final. Webhooks are planned but not live yet.

capture.ts — create and poll
const response = await fetch("https://sessionshot.com/api/captures", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ url: "https://example.com" }),
})

const { id } = await response.json()

let capture
do {
  await new Promise((resolve) => setTimeout(resolve, 2000))
  capture = await fetch("https://sessionshot.com/api/captures/" + id, {
    headers: { Authorization: "Bearer YOUR_API_KEY" },
  }).then((res) => res.json())
} while (capture.status === "queued" || capture.status === "processing")

console.log(capture.status, capture.result?.url)

Error codes

Errors return JSON: { "error": { "code", "message" } }.

CodeHTTPMeaning
VALIDATION_ERROR400Invalid body, non-HTTPS URL, or unknown fields.
UNAUTHENTICATED401Missing/malformed Authorization header, unknown or revoked key.
DOMAIN_NOT_ALLOWED403Target hostname is not on your active allowed domains.
NOT_FOUND404Capture ID does not exist or belongs to another key.
PAYLOAD_TOO_LARGE413Request body exceeds the size limit.
RATE_LIMITED429Too many requests — retry with backoff.
INTERNAL500Server-side failure.