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.
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"
}
}'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
| Field | Type | Notes |
|---|---|---|
url | string, required | Target page. Must be HTTPS. |
output.format | "png" | "jpeg" | "pdf", default "png" | Output type. |
output.fullPage | boolean, default false | Capture the full scroll height instead of the viewport. |
output.width / output.height | int, defaults 1280 × 800 | Viewport size. Width 320–3840, height 240–4320. |
output.quality | int 1–100, optional | JPEG only. |
auth.cookies | array, max 50, optional | Session cookies to inject (name, value, domain, …). |
auth.localStorage | array of { key, value }, max 50, optional | localStorage entries set before navigation. |
auth.headers | object, max 20 keys, optional | Extra request headers. Sensitive header names (like authorization or cookie) are blocked — use the dedicated fields. |
actions | array, max 20, optional | Ordered browser steps: waitForSelector { selector, timeout ≤ 15000 }, click { selector }, type { selector, text ≤ 1000 }, scroll { y ≤ 50000 }, wait { ms ≤ 10000 }. |
redaction | object, optional | selectors (max 50) masked before rendering; mode is "block" (default) or "blur". |
metadata | object, optional | Your 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.
curl "https://sessionshot.com/api/captures/CAPTURE_ID" \
-H "Authorization: Bearer YOUR_API_KEY"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
}
}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
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.
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" } }.
| Code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | Invalid body, non-HTTPS URL, or unknown fields. |
UNAUTHENTICATED | 401 | Missing/malformed Authorization header, unknown or revoked key. |
DOMAIN_NOT_ALLOWED | 403 | Target hostname is not on your active allowed domains. |
NOT_FOUND | 404 | Capture ID does not exist or belongs to another key. |
PAYLOAD_TOO_LARGE | 413 | Request body exceeds the size limit. |
RATE_LIMITED | 429 | Too many requests — retry with backoff. |
INTERNAL | 500 | Server-side failure. |
Related
Looking for client libraries? See SDKs — official packages are planned but not published yet.