Features Visual Diff Change Detection Scheduled Screenshots Watermark & Timestamp PDF Export API Change Alerts Full-Page Screenshots Pricing Blog How It Works Contact

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.

Base URL 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

bash
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
  }'
php
$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');
python
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']
javascript
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

bash
# 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.

HTTP Header
Authorization: Bearer YOUR_API_KEY
Accept: application/json
Content-Type: application/json
Keep your API key secret Never expose API keys in client-side code, public repositories, or URLs. If a key is compromised, revoke it immediately from your Dashboard and create a new one.

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
Exceeding limits If you downgrade or your subscription ends, monitors beyond your plan limit are set to 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.

GET /v1/projects

Returns a paginated list of your projects.

Query Parameters

ParameterTypeDescription
per_pageintegerItems per page (default: 20)
Response 200
{
  "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
  }
}
POST /v1/projects

Create a new project.

Request Body

FieldTypeRequiredDescription
namestringYesProject name (max 255 characters)
descriptionstringNoProject description (max 1000 characters)
bash
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"}'
php
$response = Http::withToken('YOUR_API_KEY')
    ->post('https://api.snapshotarchive.com/v1/projects', [
        'name' => 'My Website',
        'description' => 'Production monitoring',
    ]);

$project = $response->json('data');
python
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']
javascript
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();
GET /v1/projects/{id}

Retrieve a single project by ID.

PATCH /v1/projects/{id}

Update a project. Send only the fields you want to change.

DELETE /v1/projects/{id}

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.

GET /v1/monitors

Returns a paginated list of your monitors.

Query Parameters

ParameterTypeDescription
per_pageintegerItems per page (default: 20)
project_idintegerFilter monitors by project
Response 200
{
  "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
  }
}
POST /v1/monitors

Create a new monitor. The monitor starts capturing immediately on its schedule.

Request Body

FieldTypeRequiredDescription
urlstringYesURL to monitor (max 2048 chars)
namestringNoDisplay name (max 255 chars)
project_idintegerNoProject to assign monitor to
frequency_minutesintegerYesCapture interval in minutes (min: 30, subject to plan limits)
device_typestringYesdesktop or mobile
viewport_widthintegerYesBrowser width in pixels (320–3840)
viewport_heightintegerYesBrowser height in pixels (480–2160)
full_pagebooleanNoCapture the full scrollable page (default: false)
delay_secondsintegerNoWait before capture, 0–30 seconds
wait_for_selectorstringNoCSS selector to wait for before capture
click_selectorstringNoCSS selector to click before capture
clip_selectorstringNoCSS selector to clip screenshot to a specific element
hide_selectorsarrayNoCSS selectors to hide (e.g. cookie banners)
disable_animationsbooleanNoDisable CSS animations/transitions
http_auth_userstringNoHTTP Basic Auth username
http_auth_passwordstringNoHTTP Basic Auth password
cookiesarrayNoCustom cookies (max 20). Each: {name, value, domain}
diff_threshold_percentnumberNoVisual diff sensitivity, 0–100 (default: 50)
watermark_enabledbooleanNoAdd timestamp watermark to screenshots
alert_enabledbooleanNoEnable change alerts
alert_emailstringNoEmail for change notifications
alert_webhook_urlstringNoWebhook URL for change notifications
alert_slack_webhook_urlstringNoSlack webhook URL for notifications
bash
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]"
  }'
php
$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');
python
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']
javascript
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();
GET /v1/monitors/{id}

Retrieve a single monitor with its latest snapshot.

PATCH /v1/monitors/{id}

Update a monitor. Send only the fields you want to change.

bash
# 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 /v1/monitors/{id}

Delete a monitor and all its snapshots. Returns 204 No Content.

POST /v1/monitors/{id}/trigger

Trigger an immediate snapshot capture. Returns 202 Accepted — the snapshot is processed asynchronously.

Response 202
{
  "data": {
    "message": "Snapshot queued successfully.",
    "snapshot_id": "9e8f7a6b-5c4d-3e2f-1a0b-9c8d7e6f5a4b"
  }
}
Async capture After triggering, poll 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.

GET /v1/monitors/{monitorId}/snapshots

Returns a paginated list of snapshots for a specific monitor, newest first.

Query Parameters

ParameterTypeDescription
per_pageintegerItems per page (default: 20)
Response 200
{
  "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
  }
}
GET /v1/snapshots/{uuid}

Retrieve a snapshot with signed download URLs for all available files.

Response 200
{
  "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

GET /v1/snapshots/{uuid}/screenshot

Download the screenshot image. If the monitor has watermarking enabled, the watermark is applied dynamically. Otherwise, redirects to a signed storage URL.

GET /v1/snapshots/{uuid}/pdf

Download the PDF export. Requires Starter plan or above.

GET /v1/snapshots/{uuid}/html

Download the HTML source. Requires Starter plan or above.

GET /v1/snapshots/{uuid}/package

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:

bash
#!/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
php
// 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'];
}
python
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)
javascript
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.

GET /v1/monitors/{monitorId}/diffs

Returns a paginated list of diffs for a specific monitor.

Query Parameters

ParameterTypeDescription
per_pageintegerItems per page (default: 20)
Response 200
{
  "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
  }
}
GET /v1/diffs/{id}

Retrieve a single diff with a signed URL for the diff image.

Response 200
{
  "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.

GET /v1/account

Returns your account profile and active plan.

Response 200
{
  "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.

GET /v1/account/usage

Returns current usage for monitors, API keys, and today's snapshot count.

Response 200
{
  "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

ParameterTypeDescription
pageintegerPage number (default: 1)
per_pageintegerItems per page (default: 20)
Pagination meta
{
  "meta": {
    "current_page": 2,
    "per_page": 20,
    "total": 85,
    "last_page": 5
  }
}
bash
# 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.

HeaderDescription
X-RateLimit-LimitMax requests per minute
X-RateLimit-RemainingRemaining requests in current window
Retry-AfterSeconds 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 Response
{
  "error": {
    "code": "monitor_limit_exceeded",
    "message": "You have reached the maximum number of monitors for your plan.",
    "status": 422
  }
}

HTTP Status Codes

CodeMeaning
200Success
201Resource created
202Request accepted (async processing)
204Deleted successfully (no content)
401Invalid or missing API key
403Feature not available on your plan
404Resource not found
422Validation error or plan limit exceeded
429Rate limit exceeded
500Internal server error

Error Codes Reference

Error CodeHTTPDescription
monitor_limit_exceeded422Monitor count exceeds your plan limit. Upgrade or delete existing monitors.
frequency_not_allowed422Requested frequency is lower than your plan allows.
feature_not_available403Your plan does not include this feature (PDF, HTML export, etc.).
not_found404The requested resource or file does not exist.
Validation errors For 422 responses from validation, the response includes a message field with details and an errors object mapping field names to error messages.
Validation Error 422
{
  "message": "The url field is required.",
  "errors": {
    "url": ["The url field is required."],
    "frequency_minutes": ["The frequency minutes field is required."]
  }
}