Getting Started
The Snapshot Archive API lets you capture website screenshots, manage monitors, retrieve snapshots, and detect visual changes — all programmatically. API access is available on Starter plans and above.
https://api.snapshotarchive.com/v1
Quick Start
Three steps to capture your first screenshot via the API.
1. Get your API key
Go to Dashboard → API Keys and create a new key. Copy it — you won't see it again.
2. Create a monitor
curl -X POST https://api.snapshotarchive.com/v1/monitors \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"url": "https://example.com",
"frequency_minutes": 720,
"device_type": "desktop",
"viewport_width": 1920,
"viewport_height": 1080
}'
$response = Http::withToken('YOUR_API_KEY')
->post('https://api.snapshotarchive.com/v1/monitors', [
'url' => 'https://example.com',
'frequency_minutes' => 720,
'device_type' => 'desktop',
'viewport_width' => 1920,
'viewport_height' => 1080,
]);
$monitor = $response->json('data');
import requests
response = requests.post(
'https://api.snapshotarchive.com/v1/monitors',
headers={
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json',
},
json={
'url': 'https://example.com',
'frequency_minutes': 720,
'device_type': 'desktop',
'viewport_width': 1920,
'viewport_height': 1080,
}
)
monitor = response.json()['data']
const response = await fetch('https://api.snapshotarchive.com/v1/monitors', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
url: 'https://example.com',
frequency_minutes: 720,
device_type: 'desktop',
viewport_width: 1920,
viewport_height: 1080,
}),
});
const { data: monitor } = await response.json();
3. Trigger a snapshot
# Trigger an immediate snapshot (returns 202 Accepted)
curl -X POST https://api.snapshotarchive.com/v1/monitors/MONITOR_ID/trigger \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
# Poll for the result
curl https://api.snapshotarchive.com/v1/snapshots/SNAPSHOT_ID \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
The trigger endpoint returns 202 Accepted with a snapshot_id. The snapshot is captured asynchronously — poll the snapshot endpoint until status changes from pending to completed or failed.
Authentication
All API requests require a Bearer token in the Authorization header. Generate API keys from your Dashboard.
Authorization: Bearer YOUR_API_KEY
Accept: application/json
Content-Type: application/json
Every response includes JSON. Always send Accept: application/json to ensure proper error formatting. For POST/PATCH requests, also send Content-Type: application/json.
Plan Limits
API access is available on Starter plans and above. Each plan has different limits for monitors, capture frequency, and data retention.
| Feature | Free | Starter | Pro | Growth | Business |
|---|---|---|---|---|---|
| API Access | — | ✓ | ✓ | ✓ | ✓ |
| Monitors | 3 | 20 | 50 | 100 | 200 |
| Min Frequency | Daily | Every 12h | Every 6h | Hourly | Every 30 min |
| Retention | 30 days | 90 days | 1 year | 2 years | 3 years |
| Visual Diff | — | ✓ | ✓ | ✓ | ✓ |
| PDF / HTML Export | — | ✓ | ✓ | ✓ | ✓ |
| API Keys | 0 | 5 | 5 | 5 | 5 |
| Price | Free | $14/mo | $39/mo | $79/mo | $129/mo |
plan_exceeded status. Upgrade your plan to reactivate them.
Projects
Projects let you organize monitors into groups. Every account has a default project. Monitors can optionally belong to a project.
Returns a paginated list of your projects.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
per_page | integer | Items per page (default: 20) |
{
"data": [
{
"id": 1,
"name": "My Website",
"description": "Production site monitoring",
"is_default": true,
"monitors_count": 5,
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-01-15T10:30:00Z"
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total": 1,
"last_page": 1
}
}
Create a new project.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Project name (max 255 characters) |
description | string | No | Project description (max 1000 characters) |
curl -X POST https://api.snapshotarchive.com/v1/projects \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"name": "My Website", "description": "Production monitoring"}'
$response = Http::withToken('YOUR_API_KEY')
->post('https://api.snapshotarchive.com/v1/projects', [
'name' => 'My Website',
'description' => 'Production monitoring',
]);
$project = $response->json('data');
response = requests.post(
'https://api.snapshotarchive.com/v1/projects',
headers={'Authorization': 'Bearer YOUR_API_KEY', 'Accept': 'application/json'},
json={'name': 'My Website', 'description': 'Production monitoring'}
)
project = response.json()['data']
const response = await fetch('https://api.snapshotarchive.com/v1/projects', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
name: 'My Website',
description: 'Production monitoring',
}),
});
const { data: project } = await response.json();
Retrieve a single project by ID.
Update a project. Send only the fields you want to change.
Delete a project. Returns 204 No Content on success.
Monitors
Monitors are the core resource. Each monitor tracks a URL and captures screenshots on a schedule. You can configure viewport size, device type, authentication, and many other capture options.
Returns a paginated list of your monitors.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
per_page | integer | Items per page (default: 20) |
project_id | integer | Filter monitors by project |
{
"data": [
{
"id": 42,
"project_id": 1,
"url": "https://example.com",
"name": "Example Homepage",
"status": "active",
"frequency_minutes": 720,
"viewport_width": 1920,
"viewport_height": 1080,
"full_page": false,
"device_type": "desktop",
"diff_enabled": true,
"diff_threshold_percent": 50,
"watermark_enabled": false,
"alert_enabled": false,
"last_snapshot_at": "2026-07-24T08:00:00Z",
"next_snapshot_at": "2026-07-24T20:00:00Z",
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-07-24T08:00:05Z",
"project": {
"id": 1,
"name": "My Website"
}
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total": 5,
"last_page": 1
}
}
Create a new monitor. The monitor starts capturing immediately on its schedule.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | URL to monitor (max 2048 chars) |
name | string | No | Display name (max 255 chars) |
project_id | integer | No | Project to assign monitor to |
frequency_minutes | integer | Yes | Capture interval in minutes (min: 30, subject to plan limits) |
device_type | string | Yes | desktop or mobile |
viewport_width | integer | Yes | Browser width in pixels (320–3840) |
viewport_height | integer | Yes | Browser height in pixels (480–2160) |
full_page | boolean | No | Capture the full scrollable page (default: false) |
delay_seconds | integer | No | Wait before capture, 0–30 seconds |
wait_for_selector | string | No | CSS selector to wait for before capture |
click_selector | string | No | CSS selector to click before capture |
clip_selector | string | No | CSS selector to clip screenshot to a specific element |
hide_selectors | array | No | CSS selectors to hide (e.g. cookie banners) |
disable_animations | boolean | No | Disable CSS animations/transitions |
http_auth_user | string | No | HTTP Basic Auth username |
http_auth_password | string | No | HTTP Basic Auth password |
cookies | array | No | Custom cookies (max 20). Each: {name, value, domain} |
diff_threshold_percent | number | No | Visual diff sensitivity, 0–100 (default: 50) |
watermark_enabled | boolean | No | Add timestamp watermark to screenshots |
alert_enabled | boolean | No | Enable change alerts |
alert_email | string | No | Email for change notifications |
alert_webhook_url | string | No | Webhook URL for change notifications |
alert_slack_webhook_url | string | No | Slack webhook URL for notifications |
curl -X POST https://api.snapshotarchive.com/v1/monitors \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"url": "https://example.com",
"name": "Example Homepage",
"project_id": 1,
"frequency_minutes": 360,
"device_type": "desktop",
"viewport_width": 1920,
"viewport_height": 1080,
"full_page": true,
"hide_selectors": [".cookie-banner", "#popup"],
"diff_threshold_percent": 30,
"alert_enabled": true,
"alert_email": "[email protected]"
}'
$response = Http::withToken('YOUR_API_KEY')
->post('https://api.snapshotarchive.com/v1/monitors', [
'url' => 'https://example.com',
'name' => 'Example Homepage',
'project_id' => 1,
'frequency_minutes' => 360,
'device_type' => 'desktop',
'viewport_width' => 1920,
'viewport_height' => 1080,
'full_page' => true,
'hide_selectors' => ['.cookie-banner', '#popup'],
'diff_threshold_percent' => 30,
'alert_enabled' => true,
'alert_email' => '[email protected]',
]);
$monitor = $response->json('data');
response = requests.post(
'https://api.snapshotarchive.com/v1/monitors',
headers={'Authorization': 'Bearer YOUR_API_KEY', 'Accept': 'application/json'},
json={
'url': 'https://example.com',
'name': 'Example Homepage',
'project_id': 1,
'frequency_minutes': 360,
'device_type': 'desktop',
'viewport_width': 1920,
'viewport_height': 1080,
'full_page': True,
'hide_selectors': ['.cookie-banner', '#popup'],
'diff_threshold_percent': 30,
'alert_enabled': True,
'alert_email': '[email protected]',
}
)
monitor = response.json()['data']
const response = await fetch('https://api.snapshotarchive.com/v1/monitors', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
url: 'https://example.com',
name: 'Example Homepage',
project_id: 1,
frequency_minutes: 360,
device_type: 'desktop',
viewport_width: 1920,
viewport_height: 1080,
full_page: true,
hide_selectors: ['.cookie-banner', '#popup'],
diff_threshold_percent: 30,
alert_enabled: true,
alert_email: '[email protected]',
}),
});
const { data: monitor } = await response.json();
Retrieve a single monitor with its latest snapshot.
Update a monitor. Send only the fields you want to change.
# Pause a monitor
curl -X PATCH https://api.snapshotarchive.com/v1/monitors/42 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"status": "paused"}'
Delete a monitor and all its snapshots. Returns 204 No Content.
Trigger an immediate snapshot capture. Returns 202 Accepted — the snapshot is processed asynchronously.
{
"data": {
"message": "Snapshot queued successfully.",
"snapshot_id": "9e8f7a6b-5c4d-3e2f-1a0b-9c8d7e6f5a4b"
}
}
GET /v1/snapshots/{snapshot_id} until status changes from pending to completed or failed. Typical capture time is 10–30 seconds.
Snapshots
Snapshots are the captured screenshots. Each snapshot includes the screenshot image, and optionally a PDF export and HTML source.
Returns a paginated list of snapshots for a specific monitor, newest first.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
per_page | integer | Items per page (default: 20) |
{
"data": [
{
"id": "9e8f7a6b-5c4d-3e2f-1a0b-9c8d7e6f5a4b",
"monitor_id": 42,
"status": "completed",
"http_status": 200,
"response_time_ms": 1250,
"page_weight_bytes": 2456789,
"error_message": null,
"captured_at": "2026-07-24T08:00:15Z",
"created_at": "2026-07-24T08:00:00Z"
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total": 150,
"last_page": 8
}
}
Retrieve a snapshot with signed download URLs for all available files.
{
"data": {
"id": "9e8f7a6b-5c4d-3e2f-1a0b-9c8d7e6f5a4b",
"monitor_id": 42,
"status": "completed",
"http_status": 200,
"response_time_ms": 1250,
"page_weight_bytes": 2456789,
"meta_data": {
"title": "Example Domain",
"description": "This domain is for use in examples..."
},
"error_message": null,
"captured_at": "2026-07-24T08:00:15Z",
"created_at": "2026-07-24T08:00:00Z",
"files": {
"screenshot_url": "https://snapshots.snapshotarchive.com/...",
"pdf_url": "https://snapshots.snapshotarchive.com/...",
"html_url": "https://snapshots.snapshotarchive.com/..."
}
}
}
Download Files
Download the screenshot image. If the monitor has watermarking enabled, the watermark is applied dynamically. Otherwise, redirects to a signed storage URL.
Download the PDF export. Requires Starter plan or above.
Download the HTML source. Requires Starter plan or above.
Download all files (screenshot, PDF, HTML) as a ZIP archive. Requires Starter plan or above.
Polling Example
After triggering a snapshot, poll until it's ready:
#!/bin/bash
# Trigger snapshot
RESPONSE=$(curl -s -X POST https://api.snapshotarchive.com/v1/monitors/42/trigger \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json")
SNAPSHOT_ID=$(echo $RESPONSE | jq -r '.data.snapshot_id')
# Poll until completed
while true; do
STATUS=$(curl -s https://api.snapshotarchive.com/v1/snapshots/$SNAPSHOT_ID \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" | jq -r '.data.status')
echo "Status: $STATUS"
if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then
break
fi
sleep 5
done
// Trigger snapshot
$trigger = Http::withToken('YOUR_API_KEY')
->post('https://api.snapshotarchive.com/v1/monitors/42/trigger');
$snapshotId = $trigger->json('data.snapshot_id');
// Poll until completed
do {
sleep(5);
$snapshot = Http::withToken('YOUR_API_KEY')
->get("https://api.snapshotarchive.com/v1/snapshots/{$snapshotId}")
->json('data');
} while (in_array($snapshot['status'], ['pending', 'processing']));
// Download screenshot
if ($snapshot['status'] === 'completed') {
$screenshotUrl = $snapshot['files']['screenshot_url'];
}
import time
import requests
API_KEY = 'YOUR_API_KEY'
BASE = 'https://api.snapshotarchive.com/v1'
headers = {'Authorization': f'Bearer {API_KEY}', 'Accept': 'application/json'}
# Trigger snapshot
trigger = requests.post(f'{BASE}/monitors/42/trigger', headers=headers)
snapshot_id = trigger.json()['data']['snapshot_id']
# Poll until completed
while True:
time.sleep(5)
snap = requests.get(f'{BASE}/snapshots/{snapshot_id}', headers=headers).json()['data']
if snap['status'] in ('completed', 'failed'):
break
# Download screenshot
if snap['status'] == 'completed':
screenshot_url = snap['files']['screenshot_url']
img = requests.get(screenshot_url)
with open('screenshot.png', 'wb') as f:
f.write(img.content)
const API_KEY = 'YOUR_API_KEY';
const BASE = 'https://api.snapshotarchive.com/v1';
const headers = {
'Authorization': `Bearer ${API_KEY}`,
'Accept': 'application/json',
};
// Trigger snapshot
const trigger = await fetch(`${BASE}/monitors/42/trigger`, {
method: 'POST', headers,
});
const { data: { snapshot_id } } = await trigger.json();
// Poll until completed
const sleep = ms => new Promise(r => setTimeout(r, ms));
let snapshot;
do {
await sleep(5000);
const res = await fetch(`${BASE}/snapshots/${snapshot_id}`, { headers });
snapshot = (await res.json()).data;
} while (['pending', 'processing'].includes(snapshot.status));
// Download screenshot
if (snapshot.status === 'completed') {
const img = await fetch(snapshot.files.screenshot_url);
// save to file or process the image
}
Diffs
Visual diffs compare consecutive snapshots to detect changes. Each diff includes a change percentage and a highlighted diff image showing what changed.
Returns a paginated list of diffs for a specific monitor.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
per_page | integer | Items per page (default: 20) |
{
"data": [
{
"id": 789,
"monitor_id": 42,
"change_percent": 12.5,
"pixel_count_changed": 28500,
"pixel_count_total": 228000,
"is_significant": true,
"created_at": "2026-07-24T08:00:20Z",
"snapshot_before": {
"id": "abc12345-...",
"captured_at": "2026-07-23T08:00:15Z"
},
"snapshot_after": {
"id": "def67890-...",
"captured_at": "2026-07-24T08:00:15Z"
}
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total": 30,
"last_page": 2
}
}
Retrieve a single diff with a signed URL for the diff image.
{
"data": {
"id": 789,
"monitor_id": 42,
"change_percent": 12.5,
"pixel_count_changed": 28500,
"pixel_count_total": 228000,
"is_significant": true,
"diff_image_url": "https://snapshots.snapshotarchive.com/...",
"created_at": "2026-07-24T08:00:20Z",
"snapshot_before": {
"id": "abc12345-...",
"captured_at": "2026-07-23T08:00:15Z"
},
"snapshot_after": {
"id": "def67890-...",
"captured_at": "2026-07-24T08:00:15Z"
}
}
}
Account Info
Retrieve your account details and current plan information.
Returns your account profile and active plan.
{
"data": {
"id": 1,
"name": "John Doe",
"email": "[email protected]",
"timezone": "America/New_York",
"created_at": "2026-01-15T10:30:00Z",
"plan": {
"name": "Pro",
"slug": "pro",
"monitors_limit": 50,
"retention_days": 365,
"min_frequency_minutes": 360,
"api_access": true,
"diff_enabled": true,
"snapshot_package": true
}
}
}
Usage Stats
Check your current resource usage against plan limits.
Returns current usage for monitors, API keys, and today's snapshot count.
{
"data": {
"monitors": {
"used": 12,
"limit": 50,
"remaining": 38
},
"api_keys": {
"used": 2,
"limit": 5,
"remaining": 3
},
"snapshots_today": 24
}
}
Pagination
All list endpoints return paginated results. Use the meta object to navigate through pages.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (default: 1) |
per_page | integer | Items per page (default: 20) |
{
"meta": {
"current_page": 2,
"per_page": 20,
"total": 85,
"last_page": 5
}
}
# Get page 3 with 50 items per page
curl "https://api.snapshotarchive.com/v1/monitors?page=3&per_page=50" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
Rate Limits
API requests are rate-limited to ensure fair usage. Rate limit information is included in response headers.
| Header | Description |
|---|---|
X-RateLimit-Limit | Max requests per minute |
X-RateLimit-Remaining | Remaining requests in current window |
Retry-After | Seconds to wait (only on 429 responses) |
If you exceed the rate limit, you'll receive a 429 Too Many Requests response. Wait for the duration indicated in the Retry-After header before making another request.
Error Codes
All errors return a consistent JSON structure with an error code, human-readable message, and HTTP status.
{
"error": {
"code": "monitor_limit_exceeded",
"message": "You have reached the maximum number of monitors for your plan.",
"status": 422
}
}
HTTP Status Codes
| Code | Meaning |
|---|---|
200 | Success |
201 | Resource created |
202 | Request accepted (async processing) |
204 | Deleted successfully (no content) |
401 | Invalid or missing API key |
403 | Feature not available on your plan |
404 | Resource not found |
422 | Validation error or plan limit exceeded |
429 | Rate limit exceeded |
500 | Internal server error |
Error Codes Reference
| Error Code | HTTP | Description |
|---|---|---|
monitor_limit_exceeded | 422 | Monitor count exceeds your plan limit. Upgrade or delete existing monitors. |
frequency_not_allowed | 422 | Requested frequency is lower than your plan allows. |
feature_not_available | 403 | Your plan does not include this feature (PDF, HTML export, etc.). |
not_found | 404 | The requested resource or file does not exist. |
422 responses from validation, the response includes a message field with details and an errors object mapping field names to error messages.
{
"message": "The url field is required.",
"errors": {
"url": ["The url field is required."],
"frequency_minutes": ["The frequency minutes field is required."]
}
}