API reference
JSON REST API | an API key is all you need to get started. Two modes: hosted (Shotbot stores the images) or callback (images delivered to your server, white-label). Unfamiliar terms? See the glossary.
Where to start?
APIv2 Quickstart
JSON REST, hosted mode. Shotbot stores and serves images via CDN. The fastest way to get started.
View →Callback mode · White-label
Screenshots delivered to your server via HTTP POST. No Shotbot URLs, no branding. Included free in all accounts.
View →Batch mode
Up to 500 URLs per request, global callback. Used for e-commerce, archiving, and monitoring.
View →Code examples
PHP, Python, Node.js, Go, cURL, Ruby. Covers v1, v2, batch and callback.
View page →API Reference
Full OpenAPI spec - all endpoints, parameters and error codes. Import directly into Postman, Insomnia, or your AI coding tool.
View reference →API Status
Queue per queue, average time, real-time ETA, services up/down.
View status →Shotbot generates and hosts the screenshot. You receive a token to track the status and retrieve the direct image URL.
1. Submit a capture
POST https://api.shotbot.net/capture
Content-Type: application/json
{
"key": "your_api_key",
"url": "https://test.shotbot.net",
"format": "webp",
"viewport_width": 1280,
"ratio": "16:9",
"wait_time": 15
}
Response
{
"token": "4XyZaB…32 alphanumeric characters [A-Za-z0-9]",
"status": "queued",
"eta_seconds": 20
}
If monthly quota and credits are exhausted, the response will be
{"status":"waitlisted"} without a token | the capture will be processed at the next monthly reset.
2. Poll for status
GET https://api.shotbot.net/capture/a3f1c9…
// Pending
{ "token": "a3f1c9…", "status": "queued", "eta_seconds": 18 }
// Done
{
"token": "a3f1c9…",
"status": "done",
"image": "https://static.shotbot.net/a/a3/a3f1c9….webp",
"preview": "https://static.shotbot.net/a/a3/a3f1c9…_p.webp",
"format": "webp",
"url": "https://test.shotbot.net",
"meta_http_status": 200,
"meta_final_url": "https://test.shotbot.net/",
"meta_page_title": "Example Domain",
"file_hash": "sha256:e3b0c44298fc1c149afb…",
"captured_at": 1746268800
}
// Failed (status=3)
{
"token": "a3f1c9…",
"status": "failed",
"error_code": "nav_timeout",
"url": "https://test.shotbot.net",
"image": "https://static.shotbot.net/a/a3/a3f1c9….jpg",
"partial": true
}
The token acts as a bearer: anyone who has it can check the status. No API key is required for this GET endpoint.
3. Available parameters
| Field | Type | Default | Description |
|---|---|---|---|
| Essentials | |||
key | string | · | API key (required) |
url | string | · | URL to capture, http or https (required) |
| Image & output | |||
format | string | jpg | jpg · png · webp · avif · pdf (see PDF output). Without Shotbot Pro: jpg only. |
viewport_width | int | 1280 | Viewport: mobile (360 · 375 · 390 · 414 · 430) · tablet (768 · 1024) · desktop (1280 · 1440 · 1920). Without Shotbot Pro, limited to 390, 768, 1280. Shotbot Pro can also pass any custom width 280 to 3840 px (CSS / responsive testing). |
output_size | int | = viewport | Output image width: 120 · 160 · 240 · 320 · 390 · 480 · 640 · 768 · 1024 · 1200 · 1280 · 1440 · 1920 |
ratio | string | 16:9 | Aspect ratio of the capture (see table below) |
crop_height | int | · | Custom output height in pixels (overrides ratio). Sets the viewport height and captures that region from the top, so bottom-anchored fixed elements stay at the bottom. Maximum: 30,000 px. Pro only. |
fullpage | bool | false | Capture full page height (full scroll, max 30,000 px), ignores ratio |
hidpi | bool | false | HiDPI / Retina mode (resolution ×2) |
prefers_reduced_motion | bool | false | Emulates the CSS media query prefers-reduced-motion: reduce. Well-built sites disable or shorten their animations (framer-motion, GSAP, AOS, IntersectionObserver): cleaner captures, no mid-animation frames. No effect on sites that don't honour the media query. Available to all accounts. |
color_scheme | string | · | Force a color scheme before capture: "dark" · "light" · omit for browser default |
selector | string | · | CSS selector of the element to capture. Only that element is extracted, returned at its natural size. output_size, ratio and crop_height are not applied (no re-crop or rescale). Ignored when format: "pdf". Fails with nav_error if no element matches. Pro only. |
| Behaviour | |||
nojs | bool | false | Disables JavaScript | bypasses pop-ups, cookie banners and consent overlays |
wait | int | 5 | Seconds to wait before capture, 0-30. Increase for React/Vue/Angular SPAs and lazy-loading. Navigation timeout aligns automatically. |
dismiss_cookies | string | "" | Auto-dismisses CMP banners (OneTrust, Cookiebot, Didomi, TCF…). Values: "" (leave visible) · "reject" (prefer reject) · "accept" (accept to close). Pro only for non-empty values. |
scroll_before_capture | bool | false | Forces full page scroll before capture. Triggers loading="lazy" images and IntersectionObserver pipelines, useful combined with fullpage. Pro only. |
scroll_offset_px | int | 0 | Scroll down N px (0 to 30,000) before capture, so the frame starts lower. Runs after scroll_before_capture. Mutually exclusive with scroll_to (sending both returns 400 invalid_scroll_combo). Ignored for fullpage, PDF and selector. Pro only. |
scroll_to | string | · | CSS selector scrolled into view (top of frame) before capture. Unlike selector it does not clip output, it only positions the page. Mutually exclusive with scroll_offset_px (400 invalid_scroll_combo). A selector matching no element is a no-op (capture succeeds). Ignored for fullpage, PDF and selector. Pro only. |
block_ads | bool | false | Blocks requests to the most common ad networks and trackers, faster and cleaner captures. Pro only. |
autoplay_videos | bool | false | Launches Chromium with --autoplay-policy=no-user-gesture-required so <video autoplay> elements, including unmuted ones, start playing without a user gesture. Useful for pages with background or hero videos that would otherwise show a static poster frame. No effect on PDF captures. Pro only. |
omit_background | bool | false | Transparent viewport backdrop (Chromium omitBackground option). The page's own background colour is preserved; only the default white backdrop becomes transparent. Visible on PNG/WebP/AVIF; JPG flattens the alpha to black (silent no-op). No effect on PDF output. Pro only. |
emulate_print_media | bool | false | Applies the page's @media print stylesheet before capture (page.emulateMediaType('print')). Useful for capturing print-only layouts as an image. No additional effect on PDF (already in print mode). Pro only. |
render_region | string | fr-paris | Render region: fr-paris (default, free) · ca-montreal · sg-singapore · au-sydney · vn-hanoi. The capture is routed through a network egress point in the chosen country, locale and timezone emulated. See geolocated screenshots. Pro only for values other than fr-paris. |
| Auth / Session | |||
http_auth | object | · | HTTP Basic authentication (see below). Pro only. |
cookies | array | · | Cookies to inject before navigation (see below). Useful for capturing authenticated pages without HTTP Basic Auth. Max 50 cookies. Pro only. |
| Privacy | |||
private | bool | false | Private capture: the image is not uploaded to the CDN. The GET /capture/{token} response contains "download" (URL pointing to /capture/{token}/file) instead of "image"/"preview". The file is fetchable any number of times until it auto-expires, then purged. Deduplication disabled. Available to all accounts. |
These options in pictures: viewports · presets · full page · frames · cookie banners · light/dark theme.
Available ratios
| Value | Orientation | Typical use |
|---|---|---|
16:9 | Landscape | HD screen, video (default) |
4:3 | Landscape | Classic standard |
16:10 | Landscape | PC monitors |
3:2 | Landscape | Photography |
2:1 | Landscape | Panoramic, hero |
1:1 | Square | Social media |
9:16 | Portrait | Mobile, stories |
3:4 | Portrait | Standard portrait |
10:16 | Portrait | Tablet portrait |
2:3 | Portrait | Photo portrait |
1:2 | Portrait | Long portrait |
PDF output (format: "pdf")
With format: "pdf", Shotbot produces a PDF document instead of a raster image.
Image-specific options (output_size, ratio, crop_height, fullpage) are ignored:
layout is governed by page size, orientation and scale.
A 640 px WebP thumbnail is generated in parallel for the dashboard and preview links.
| Field | Type | Default | Description |
|---|---|---|---|
pdf_page_size | string | A4 | A4 · A3 · A5 · Letter · Legal · Tabloid |
pdf_margin_mm | int | 10 | Uniform margin applied to all 4 sides (0-50 mm) |
pdf_scale | float | 1.00 | Rendering scale factor (0.10-2.00) |
pdf_landscape | bool | false | Landscape orientation if true, portrait otherwise |
{
"key": "YOUR_API_KEY",
"url": "https://test.shotbot.net/invoice/12345",
"format": "pdf",
"pdf_page_size": "A4",
"pdf_margin_mm": 12,
"pdf_scale": 1.00,
"pdf_landscape": false
}
The PDF is served at https://static.shotbot.net/{t1}/{t2}/{token}.pdf with
Content-Type: application/pdf. The WebP thumbnail remains accessible at
https://cache.shotbot.net/t/{token}/preview.
HTTP Basic authentication (http_auth)
To capture pages protected by a password (HTTP 401):
{
"key": "your_key",
"url": "https://test.shotbot.net/private-page",
"http_auth": {
"user": "login",
"pass": "password"
}
}
Credentials are erased from the database as soon as the capture completes. If a capture remains pending for more than 2 hours, they are deleted automatically. They are never returned in API responses.
Cookie injection (cookies) Pro
Inject cookies before navigation to capture authenticated pages (sessions, JWT tokens, preferences…) without HTTP Basic Auth.
{
"key": "your_key",
"url": "https://test.shotbot.net/dashboard",
"cookies": [
{ "name": "session_id", "value": "abc123xyz" },
{ "name": "lang", "value": "en", "domain": "test.shotbot.net", "path": "/" }
]
}
| Field | Type | Description |
|---|---|---|
name | string | Cookie name (required, max 255 chars) |
value | string | Cookie value (required, max 4096 chars) |
domain | string | Cookie domain (optional, defaults to target URL domain, max 255 chars) |
path | string | Cookie path (optional, defaults to "/", max 255 chars) |
PHP example: APIv2 hosted
<?php
$api_key = "YOUR_API_KEY";
$url = "https://test.shotbot.net";
// 1. Submit the capture
$ch = curl_init("https://api.shotbot.net/capture");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode([
"key" => $api_key,
"url" => $url,
"format" => "webp",
"viewport_width" => 1280,
"ratio" => "16:9",
]),
]);
$res = json_decode(curl_exec($ch), true);
curl_close($ch);
$token = $res["token"] ?? null;
if (!$token) { echo "Error: " . ($res["error"] ?? "unknown"); exit; }
// 2. Wait for result (polling)
do {
sleep(3);
$status = json_decode(file_get_contents(
"https://api.shotbot.net/capture/" . $token
), true);
} while (($status["status"] ?? "") !== "done");
echo "<img src=\"" . htmlspecialchars($status["image"]) . "\" loading=\"lazy\">";
Example with extra options (ratio, HiDPI, ad blocking, lazy-load, HTTP auth)…
<?php
$ch = curl_init("https://api.shotbot.net/capture");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode([
"key" => "YOUR_API_KEY",
"url" => "https://test.shotbot.net",
"format" => "avif",
"viewport_width" => 390, // iPhone 14 / 15
"output_size" => 390,
"ratio" => "9:16", // mobile portrait
"hidpi" => true,
"color_scheme" => "dark", // force prefers-color-scheme: dark
"block_ads" => true, // block ad networks + trackers
"dismiss_cookies" => "reject", // dismiss CMP banners
"scroll_before_capture" => true, // trigger lazy-load
"wait_time" => 20,
"http_auth" => ["user" => "admin", "pass" => "s3cr3t"],
]),
]);
$res = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $res["token"];
4. Account status
GET https://api.shotbot.net/account?key={YOUR_KEY}
// Or with Authorization header
GET https://api.shotbot.net/account
Authorization: Bearer {YOUR_KEY}
Response
{
"plan": "free", // "free" | "pro"
"pro_until": null, // Unix timestamp (Pro only)
"credit": 50, // Extra credits beyond the monthly quota
"quota_used": 12, // Captures consumed today
"quota_total": 200, // Monthly quota ceiling
"quota_remaining": 188, // Captures still available
"inflight": 1, // Captures currently queued or processing
"inflight_cap": 3 // Concurrency cap for this account tier
}
Use shotbot status in the CLI for a human-readable version of this endpoint.
Shotbot generates the screenshot and pushes it to your server in a single multipart request. No hosting on our side, images land directly with you, with no Shotbot branding. Included in the free account.
Submit a callback capture
POST https://api.shotbot.net/capture/callback
Content-Type: application/json
{
"key": "your_api_key",
"url": "https://test.shotbot.net",
"callback_url": "https://your-site.com/shotbot-callback.php",
"callback_secret": "a-random-secret",
"format": "webp",
"viewport_width": 1280,
"ratio": "16:9"
}
Immediate response
{ "token": "a3f1c9…", "status": "queued", "eta_seconds": 20 }
Reception on your server
Once the capture is complete, Shotbot sends a POST multipart/form-data to your callback_url:
$_POST["token"] → 32-char alphanumeric token (unique capture identifier)
$_POST["url"] → original captured URL
$_POST["status"] → "OK" | "ERR"
$_POST["format"] → image format (jpg, webp, etc.)
$_POST["callback_secret"] → secret echoed back as-is (only if provided on creation)
$_FILES["file"] → binary image (requested size and format)
// No preview in callback mode | one image in the requested format only.
Constraints on callback_url
- HTTPS required.
- The URL must not resolve to a private IP address (SSRF protection).
- Requests originate from IP
163.172.105.112, whitelist it in your firewall if needed. Up-to-date JSON list.
Callback-specific parameters
Same as the hosted APIv2, plus:
| Field | Type | Description |
|---|---|---|
callback_url | string | HTTPS reception URL (required for this mode) |
callback_secret | string | Optional shared secret (≤ 255 chars) echoed back as-is in the reception POST (field callback_secret); allows verifying that the request originates from Shotbot |
PHP example: callback mode
<?php
// submit-capture.php, client side
$ch = curl_init("https://api.shotbot.net/capture/callback");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode([
"key" => "YOUR_API_KEY",
"url" => "https://test.shotbot.net",
"callback_url" => "https://your-site.com/shotbot-callback.php",
"callback_secret" => "a-random-secret",
"format" => "webp",
"viewport_width" => 1280,
"ratio" => "16:9",
]),
]);
$res = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "Capture: " . $res["token"];
<?php
// shotbot-callback.php | receives the capture from Shotbot
$EXPECTED_SECRET = "a-random-secret"; // same as callback_secret sent
if (!hash_equals($EXPECTED_SECRET, $_POST["callback_secret"] ?? "")) {
http_response_code(403);
exit;
}
if (($_POST["status"] ?? "") !== "OK") {
http_response_code(200);
exit;
}
$token = preg_replace("/[^A-Za-z0-9]/", "", $_POST["token"] ?? "");
$format = in_array($_POST["format"] ?? "", ["jpg","png","webp","avif"])
? $_POST["format"] : "jpg";
if (!$token || !isset($_FILES["file"])) {
http_response_code(400);
exit;
}
$dest = __DIR__ . "/shots/" . $token . "." . $format;
move_uploaded_file($_FILES["file"]["tmp_name"], $dest);
http_response_code(200);
echo "OK";
Submit up to 500 URLs in a single request. Same options as the standard APIv2, applicable globally or per URL. Used for e-commerce (product catalogs), legal archiving (immutable timestamped snapshots, each capture remains accessible indefinitely and is never overwritten), and visual monitoring pipelines.
Limit: 500 URLs/request (Shotbot Pro: 5,000).
Planning an unusually large run (e.g. 500,000 captures at once)? Let us know before you launch - we'll prepare capacity on your end.
Submit a batch
POST https://api.shotbot.net/capture/batch
Content-Type: application/json
{
"key": "your_api_key",
"jobs": [
{ "url": "https://test.shotbot.net" },
{ "url": "https://other.com", "format": "png", "viewport_width": 390 }
],
// Global options, applied to all captures, overridable per job
"format": "webp",
"viewport_width": 1280,
"ratio": "16:9"
}
Response (HTTP 202)
{
"submitted": 2,
"waitlisted": 0,
"deduplicated": 0,
"errors": 0,
"jobs": [
{ "index": 0, "url": "https://test.shotbot.net", "token": "a3f1c9…", "status": "queued" },
{ "index": 1, "url": "https://other.com", "token": "b7e2d4…", "status": "queued" }
]
}
Possible statuses per capture
| Status | Meaning |
|---|---|
queued | Capture created, poll GET /capture/{token} for the result |
queued + "deduplicated":true | URL already submitted recently (60 s) or twice in the same batch | existing token reused, no additional quota consumed |
waitlisted | Monthly quota and credits exhausted, processed at next reset |
error | Invalid URL or incorrect parameter | other captures in the batch are not affected |
Parameters
| Field | Level | Description |
|---|---|---|
key | Root | API key (required) |
jobs | Root | Array of capture objects, max 500 (required) |
jobs[n].url | Per capture | URL to capture, http or https (required) |
format, viewport_width, output_size, ratio, crop_height, fullpage, hidpi, color_scheme, prefers_reduced_motion, nojs, wait, dismiss_cookies, scroll_before_capture, scroll_offset_px, scroll_to, block_ads, autoplay_videos, omit_background, emulate_print_media, http_auth, cookies, selector, private, pdf_page_size, pdf_margin_mm, pdf_scale, pdf_landscape | Root or per capture | Capture options (see Hosted tab for allowed values) | per-capture value takes priority over the global default. private disables deduplication: two identical URLs in the same batch produce two distinct tokens. |
callback_url | Root | If present (HTTPS, non-private): all captures switch to callback mode, images are pushed to this URL when each capture finishes |
callback_secret | Root | Optional shared secret echoed back as-is in the reception POST, useful to verify request origin (only if callback_url is set) |
idempotency_key | Root | Optional client-supplied retry-safety key (16-128 chars [A-Za-z0-9_.-]). Replaying the same batch with the same key within 24h from the same account returns the original response (header X-Idempotent-Replay: true) without creating new jobs or deducting quota. Recommended: generate a UUID before the first call, reuse it on transient retries, drop it on success. |
Global errors
| HTTP code | Error | Cause |
|---|---|---|
| 400 | missing_jobs | jobs field missing |
| 400 | jobs_not_array | jobs is not an array |
| 400 | too_many_jobs + "max":500 | More than 500 URLs |
| 400 | invalid_callback_url | Invalid callback URL, non-HTTPS or private address |
| 400 | invalid_idempotency_key | Idempotency key out of format (16-128 chars [A-Za-z0-9_.-]) |
| 401 | invalid_key | Incorrect API key |
PHP example: batch mode
<?php
$api_key = "YOUR_API_KEY";
$urls = [
"https://test.shotbot.net",
"https://github.com",
"https://wikipedia.org",
// ... up to 500
];
// Submit the batch
$ch = curl_init("https://api.shotbot.net/capture/batch");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode([
"key" => $api_key,
"format" => "webp",
"viewport_width" => 1280,
"ratio" => "16:9",
"jobs" => array_map(fn($u) => ["url" => $u], $urls),
]),
]);
$res = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "Submitted: {$res['submitted']} · Waitlisted: {$res['waitlisted']}\n";
// Collect tokens for created captures
$tokens = [];
foreach ($res["jobs"] as $capture) {
if (isset($capture["token"])) {
$tokens[$capture["url"]] = $capture["token"];
}
}
// Wait and display results
foreach ($tokens as $url => $token) {
do {
sleep(3);
$status = json_decode(file_get_contents(
"https://api.shotbot.net/capture/" . $token
), true);
} while (($status["status"] ?? "") !== "done");
echo $url . " → " . $status["image"] . "\n";
}
Batch callback, images delivered to your server…
<?php
// All captures in the batch are pushed to your callback_url
$ch = curl_init("https://api.shotbot.net/capture/batch");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode([
"key" => "YOUR_API_KEY",
"callback_url" => "https://your-site.com/shotbot-callback.php",
"format" => "webp",
"jobs" => [
["url" => "https://test.shotbot.net"],
["url" => "https://other.com", "ratio" => "1:1"],
],
]),
]);
$res = json_decode(curl_exec($ch), true);
curl_close($ch);
// No polling needed | each image will be POSTed to your callback_url
echo "Captures submitted: " . $res["submitted"];
Hosted mode (legacy)
Request
https://add.shotbot.net/k={KEY}/url={ENCODED_URL}
// With options
https://add.shotbot.net/k={KEY}/format/webp/w/1280/fullpage/1/url={URL}
Display the screenshot
<!-- Format: https://static.shotbot.net/{md5[0]}/{md5[0:2]}/{md5}/{size}.jpg -->
$md5 = md5($url);
$img = "https://static.shotbot.net/{$md5[0]}/{$md5[0]..$md5[1]}/{$md5}/120.jpg";
Output sizes (fixed 4:3 ratio)
| Size | Dimensions |
|---|---|
320 | 320×240 |
240 | 240×180 |
160 | 160×120 |
120 | 120×90 |
92 | 92×69 |
80 | 80×60 |
Options
| Parameter | Values |
|---|---|
/nojs/1/ | Disable JavaScript |
/format/webp/ | jpg · png · webp · avif |
/w/1280/ | 768 · 1024 · 1280 · 1600 |
/fullpage/1/ | Full page |
/hidpi/1/ | Retina ×2 |
/darkmode/1/ | Dark mode |
Return codes
| Code | Meaning |
|---|---|
| OK | Capture created or queued |
| OK waitlist | Quota exceeded, added to waitlist |
| ERR key | Invalid key |
| ERR args | Incorrect parameters |
Refresh a capture (update)
https://update.shotbot.net/k={KEY}/[options/]{URL}
// Same parameters as add | re-captures without deduplication
Callback mode (legacy)
Captures are generated then delivered to your server via HTTP POST.
Request (your server → Shotbot)
POST https://add.shotbot.net/callback.php
k={KEY}&uri={ENCODED_URL}&s=120&t=10&uri_callback={CALLBACK_URL}&nojs=0
Response: OK {id}. On error: standard return codes.
Reception (your server ← Shotbot)
$_POST["id"] → job identifier
$_POST["uri_md5"] → MD5 of the captured URL
$_POST["uri"] → captured URL
$_POST["width"] → requested size (e.g. 120)
$_POST["status"] → OK | ERR
$_FILES["file_120"] → JPEG image (name matches requested size)
Available sizes: 80 · 92 · 120 · 160 · 240 · 320. Your callback script must respond within 30 seconds.
Check quota (legacy)
https://status.shotbot.net/k={KEY}
// → "OK 500:327:5000" (quota:used:reserve)
A question before integrating?
Quotas, formats, ratios, callbacks, batch, scheduling: the FAQ covers the most common questions asked by developers before their first request.
Frequently asked questions