Features Visual Diff Change Detection Scheduled Screenshots Watermark & Timestamp PDF Export API Pricing Blog How It Works About Contact

Getting Started

The Snapshot Archive API lets you programmatically capture website screenshots, manage monitors, retrieve snapshots, and detect visual changes. All API access requires authentication via an API key.

Base URL https://snapshotarchive.com/api/v1

Quick Example

Trigger a snapshot capture for one of your monitors:

cURL
curl -X POST https://snapshotarchive.com/api/v1/monitors/1/trigger \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"
202 Accepted
{
  "data": {
    "message": "Snapshot queued successfully.",
    "snapshot_id": "9c8b7a6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d"
  }
}

Response Format

All responses are JSON. Successful responses wrap data in a data key. Collections include a meta key with pagination info.

Success (single resource)
{
  "data": {
    "id": 1,
    "name": "My Project",
    "created_at": "2025-01-15T10:30:00.000000Z"
  }
}
Success (collection)
{
  "data": [ ... ],
  "meta": {
    "current_page": 1,
    "per_page": 20,
    "total": 42,
    "last_page": 3
  }
}
Error
{
  "error": {
    "code": "invalid_api_key",
    "message": "The provided API key is invalid or has been revoked.",
    "status": 401
  }
}

Authentication

All API requests must include your API key in the Authorization header using the Bearer scheme.

Header
Authorization: Bearer YOUR_API_KEY

Create and manage API keys from your Dashboard → API Keys.

Keep your API keys secure. Do not share them in public repositories, client-side code, or expose them in URLs. If a key is compromised, revoke it immediately from your dashboard.

Example Requests

cURL
curl https://snapshotarchive.com/api/v1/account \
  -H "Authorization: Bearer sk_live_abc123..." \
  -H "Accept: application/json"
PHP (Laravel Http)
$response = Http::withToken('sk_live_abc123...')
    ->acceptJson()
    ->get('https://snapshotarchive.com/api/v1/account');

$account = $response->json('data');
JavaScript (fetch)
const response = await fetch('https://snapshotarchive.com/api/v1/account', {
  headers: {
    'Authorization': 'Bearer sk_live_abc123...',
    'Accept': 'application/json'
  }
});

const { data } = await response.json();
Python (requests)
import requests

response = requests.get('https://snapshotarchive.com/api/v1/account', headers={
    'Authorization': 'Bearer sk_live_abc123...',
    'Accept': 'application/json'
})

data = response.json()['data']

Projects

Projects are containers for organizing your monitors. Each project can hold multiple monitors and is automatically created from the domain name when adding a website through the dashboard.

GET /api/v1/projects

Returns a paginated list of all your projects with monitor counts.

Query Parameters

NameTypeDefaultDescription
per_pageinteger20Items per page (1-100)
pageinteger1Page number

Response

200 OK
{
  "data": [
    {
      "id": 1,
      "user_id": 1,
      "name": "example.com",
      "description": null,
      "monitors_count": 3,
      "created_at": "2025-01-15T10:30:00.000000Z",
      "updated_at": "2025-01-15T10:30:00.000000Z"
    }
  ],
  "meta": { "current_page": 1, "per_page": 20, "total": 1, "last_page": 1 }
}
POST /api/v1/projects

Create a new project.

Body Parameters

NameTypeRequiredDescription
namestringYesProject name (max 255 characters)
descriptionstringNoProject description (max 1000 characters)
cURL
curl -X POST https://snapshotarchive.com/api/v1/projects \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"name": "Marketing Sites", "description": "All marketing pages"}'
201 Created
{
  "data": {
    "id": 2,
    "user_id": 1,
    "name": "Marketing Sites",
    "description": "All marketing pages",
    "created_at": "2025-03-15T14:00:00.000000Z",
    "updated_at": "2025-03-15T14:00:00.000000Z"
  }
}
GET /api/v1/projects/{id}

Retrieve a single project by ID, including monitor count.

200 OK
{
  "data": {
    "id": 1,
    "user_id": 1,
    "name": "example.com",
    "description": null,
    "monitors_count": 3,
    "created_at": "2025-01-15T10:30:00.000000Z",
    "updated_at": "2025-01-15T10:30:00.000000Z"
  }
}
PUT /api/v1/projects/{id}

Update a project's name or description.

Body Parameters

NameTypeRequiredDescription
namestringNoNew project name (max 255 characters)
descriptionstringNoNew description (max 1000 characters)
cURL
curl -X PUT https://snapshotarchive.com/api/v1/projects/1 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"name": "Updated Name", "description": "New description"}'
DELETE /api/v1/projects/{id}

Delete a project. This will also delete all monitors and their snapshots within the project.

cURL
curl -X DELETE https://snapshotarchive.com/api/v1/projects/1 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"
204 No Content
(empty response body)

Monitors

Monitors track specific URLs and capture screenshots on a schedule. Each monitor belongs to a project and stores configuration for viewport, capture settings, and alert preferences.

GET /api/v1/monitors

Returns a paginated list of all your monitors across all projects. Includes the associated project data.

Query Parameters

NameTypeDefaultDescription
project_idintegerFilter monitors by project ID
per_pageinteger20Items per page (1-100)
pageinteger1Page number

Response

200 OK
{
  "data": [
    {
      "id": 1,
      "user_id": 1,
      "project_id": 1,
      "url": "https://example.com",
      "name": "Example Homepage",
      "status": "active",
      "frequency_minutes": 1440,
      "viewport_width": 1280,
      "viewport_height": 800,
      "device_type": "desktop",
      "full_page": true,
      "wait_for_selector": null,
      "delay_seconds": 0,
      "disable_animations": false,
      "hide_selectors": null,
      "click_selector": null,
      "http_auth_user": null,
      "diff_threshold_percent": "0.5000",
      "alert_enabled": true,
      "alert_email": "[email protected]",
      "alert_webhook_url": null,
      "alert_slack_webhook_url": null,
      "next_snapshot_at": "2025-03-16T14:00:00.000000Z",
      "last_snapshot_at": "2025-03-15T14:00:00.000000Z",
      "consecutive_errors": 0,
      "last_error_message": null,
      "created_at": "2025-01-15T10:30:00.000000Z",
      "updated_at": "2025-03-15T14:00:00.000000Z",
      "project": {
        "id": 1,
        "name": "example.com"
      }
    }
  ],
  "meta": { "current_page": 1, "per_page": 20, "total": 1, "last_page": 1 }
}
POST /api/v1/monitors

Create a new monitor to start tracking a URL. The first snapshot is captured automatically after creation.

Body Parameters

NameTypeRequiredDescription
urlstringYesFull URL to monitor (max 2048 chars)
namestringNoDisplay name (max 255 chars)
project_idintegerNoProject ID to assign the monitor to
frequency_minutesintegerNoCapture interval in minutes. Min: 5. Default depends on plan.
viewport_widthintegerNoBrowser viewport width in px (320–3840). Default: 1280
viewport_heightintegerNoBrowser viewport height in px (480–2160). Default: 800
device_typestringNodesktop or mobile. Default: desktop
full_pagebooleanNoCapture full-page screenshot. Default: true
wait_for_selectorstringNoCSS selector to wait for before capture (max 500 chars)
delay_secondsintegerNoExtra delay before capture in seconds (0–30). Default: 0
hide_selectorsarrayNoArray of CSS selectors to hide before capture (e.g. cookie banners)
click_selectorstringNoCSS selector to click before capture (max 500 chars)
diff_threshold_percentfloatNoMinimum change % to flag as significant (0–100). Default: 0.5
alert_enabledbooleanNoEnable alerts on significant changes. Default: true
alert_emailstringNoEmail address for change alerts
alert_webhook_urlstringNoWebhook URL to POST on changes
alert_slack_webhook_urlstringNoSlack incoming webhook URL for notifications
cURL
curl -X POST https://snapshotarchive.com/api/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",
    "frequency_minutes": 60,
    "full_page": true,
    "viewport_width": 1280,
    "viewport_height": 800,
    "diff_threshold_percent": 1.0,
    "alert_enabled": true,
    "alert_email": "[email protected]"
  }'
201 Created
{
  "data": {
    "id": 5,
    "user_id": 1,
    "project_id": 3,
    "url": "https://example.com",
    "name": "Example Homepage",
    "status": "active",
    "frequency_minutes": 60,
    "viewport_width": 1280,
    "viewport_height": 800,
    "device_type": "desktop",
    "full_page": true,
    "diff_threshold_percent": "1.0000",
    "alert_enabled": true,
    "alert_email": "[email protected]",
    "created_at": "2025-03-15T14:00:00.000000Z",
    "project": {
      "id": 3,
      "name": "example.com"
    }
  }
}
Your plan limits the number of monitors you can create and the minimum capture frequency. If you exceed your limit, the API returns a 422 error with code monitor_limit_exceeded.
GET /api/v1/monitors/{id}

Retrieve a single monitor with its project and latest snapshot information.

200 OK
{
  "data": {
    "id": 1,
    "url": "https://example.com",
    "name": "Example Homepage",
    "status": "active",
    "frequency_minutes": 60,
    ...
    "project": { "id": 1, "name": "example.com" },
    "latest_snapshot": {
      "id": "9c8b7a6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
      "monitor_id": 1,
      "status": "completed",
      "http_status": 200,
      "response_time_ms": 1250,
      "captured_at": "2025-03-15T14:00:00.000000Z"
    }
  }
}
PUT /api/v1/monitors/{id}

Update monitor settings. Accepts all the same parameters as create. Only provided fields are updated.

cURL
curl -X PUT https://snapshotarchive.com/api/v1/monitors/1 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"frequency_minutes": 120, "diff_threshold_percent": 2.0}'
DELETE /api/v1/monitors/{id}

Delete a monitor and all its associated snapshots and diffs.

204 No Content
(empty response body)
POST /api/v1/monitors/{id}/trigger

Manually trigger an immediate snapshot capture, regardless of the monitor's schedule. The snapshot is queued and processed asynchronously.

cURL
curl -X POST https://snapshotarchive.com/api/v1/monitors/1/trigger \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"

Response

202 Accepted
{
  "data": {
    "message": "Snapshot queued successfully.",
    "snapshot_id": "9c8b7a6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d"
  }
}
The 202 status means the capture has been queued. Use the returned snapshot_id to poll GET /api/v1/snapshots/{id} and check the status field (pendingprocessingcompleted or failed).

Snapshots

Snapshots are individual captures of a monitored URL. Each snapshot includes a screenshot image, and optionally a full-page screenshot, HTML source, and PDF. Snapshots use UUID identifiers.

GET /api/v1/monitors/{monitor_id}/snapshots

Returns a paginated list of snapshots for a specific monitor, ordered by most recent first.

Query Parameters

NameTypeDefaultDescription
per_pageinteger20Items per page (1-100)
pageinteger1Page number

Response

200 OK
{
  "data": [
    {
      "id": "9c8b7a6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
      "monitor_id": 1,
      "user_id": 1,
      "status": "completed",
      "http_status": 200,
      "response_time_ms": 1250,
      "page_weight_bytes": 524288,
      "links_internal": 45,
      "links_external": 12,
      "links_broken": 0,
      "console_errors": [],
      "screenshot_path": "monitors/1/screenshots/abc123.png",
      "thumbnail_path": "monitors/1/thumbnails/abc123.jpg",
      "pdf_path": "monitors/1/pdfs/abc123.pdf",
      "html_path": "monitors/1/html/abc123.html.gz",
      "captured_at": "2025-03-15T14:00:00.000000Z",
      "created_at": "2025-03-15T14:00:00.000000Z",
      "updated_at": "2025-03-15T14:00:05.000000Z"
    }
  ],
  "meta": { "current_page": 1, "per_page": 20, "total": 124, "last_page": 7 }
}
GET /api/v1/snapshots/{id}

Retrieve detailed information about a specific snapshot, including signed file URLs for direct download.

Response

200 OK
{
  "data": {
    "id": "9c8b7a6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
    "monitor_id": 1,
    "user_id": 1,
    "status": "completed",
    "http_status": 200,
    "http_headers": { "content-type": "text/html; charset=utf-8" },
    "response_time_ms": 1250,
    "page_weight_bytes": 524288,
    "links_internal": 45,
    "links_external": 12,
    "links_broken": 0,
    "console_errors": [],
    "captured_at": "2025-03-15T14:00:00.000000Z",
    "monitor": {
      "id": 1,
      "url": "https://example.com",
      "name": "Example Homepage"
    },
    "files": {
      "screenshot_url": "https://storage.example.com/monitors/1/screenshots/abc123.png",
      "pdf_url": "https://storage.example.com/monitors/1/pdfs/abc123.pdf",
      "html_url": "https://storage.example.com/monitors/1/html/abc123.html.gz"
    }
  }
}
The files object contains direct URLs to the stored files. Any file that is not available will have a null value.
GET /api/v1/snapshots/{id}/screenshot

Redirects to the screenshot image file (PNG format). Follow the redirect to download.

cURL
curl -L https://snapshotarchive.com/api/v1/snapshots/{id}/screenshot \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -o screenshot.png
Returns a 302 redirect to the storage URL. Use -L (follow redirects) in cURL. Returns 404 if the screenshot is not available.
GET /api/v1/snapshots/{id}/pdf

Redirects to the PDF capture of the page. Returns 404 if PDF is not available for this snapshot.

GET /api/v1/snapshots/{id}/html

Redirects to the captured HTML source of the page (gzipped). Returns 404 if HTML is not available.

GET /api/v1/snapshots/{id}/package

Download a complete snapshot package as a ZIP archive containing the screenshot, PDF, and HTML source.

cURL
curl https://snapshotarchive.com/api/v1/snapshots/{id}/package \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -o snapshot-package.zip
Unlike other file endpoints, this returns the ZIP file directly (not a redirect). The response has Content-Type: application/zip. Returns 404 if no files are available.

Diffs

Diffs represent visual comparisons between consecutive snapshots. When a change exceeds the monitor's diff_threshold_percent, it is flagged as significant and triggers alerts if configured.

GET /api/v1/monitors/{monitor_id}/diffs

Returns a paginated list of diffs for a specific monitor, including before/after snapshot timestamps.

Query Parameters

NameTypeDefaultDescription
per_pageinteger20Items per page (1-100)
pageinteger1Page number

Response

200 OK
{
  "data": [
    {
      "id": 78,
      "monitor_id": 1,
      "snapshot_before_id": "aaa-bbb-ccc",
      "snapshot_after_id": "ddd-eee-fff",
      "change_percent": "12.5000",
      "pixel_count_changed": 45230,
      "pixel_count_total": 1048576,
      "is_significant": true,
      "changed_regions": [
        { "x": 100, "y": 200, "width": 300, "height": 150 }
      ],
      "diff_image_path": "monitors/1/diffs/diff_78.png",
      "created_at": "2025-03-15T14:00:00.000000Z",
      "snapshot_before": {
        "id": "aaa-bbb-ccc",
        "captured_at": "2025-03-14T14:00:00.000000Z"
      },
      "snapshot_after": {
        "id": "ddd-eee-fff",
        "captured_at": "2025-03-15T14:00:00.000000Z"
      }
    }
  ],
  "meta": { "current_page": 1, "per_page": 20, "total": 15, "last_page": 1 }
}
GET /api/v1/diffs/{id}

Retrieve detailed diff information including the monitor, before/after snapshots, changed regions, and a direct URL to the diff overlay image.

Response

200 OK
{
  "data": {
    "id": 78,
    "monitor_id": 1,
    "snapshot_before_id": "aaa-bbb-ccc",
    "snapshot_after_id": "ddd-eee-fff",
    "change_percent": "12.5000",
    "pixel_count_changed": 45230,
    "pixel_count_total": 1048576,
    "is_significant": true,
    "changed_regions": [
      { "x": 100, "y": 200, "width": 300, "height": 150 }
    ],
    "created_at": "2025-03-15T14:00:00.000000Z",
    "diff_image_url": "https://storage.example.com/monitors/1/diffs/diff_78.png",
    "monitor": {
      "id": 1,
      "url": "https://example.com",
      "name": "Example Homepage",
      ...
    },
    "snapshot_before": {
      "id": "aaa-bbb-ccc",
      "status": "completed",
      "captured_at": "2025-03-14T14:00:00.000000Z",
      ...
    },
    "snapshot_after": {
      "id": "ddd-eee-fff",
      "status": "completed",
      "captured_at": "2025-03-15T14:00:00.000000Z",
      ...
    }
  }
}

Account Info

Retrieve your account details and current plan configuration.

GET /api/v1/account

Get your account details, timezone, and full plan information.

Response

200 OK
{
  "data": {
    "id": 1,
    "name": "John Doe",
    "email": "[email protected]",
    "timezone": "Europe/Berlin",
    "created_at": "2025-01-01T00:00:00.000000Z",
    "plan": {
      "name": "Pro",
      "slug": "pro",
      "monitors_limit": 50,
      "retention_days": 90,
      "min_frequency_minutes": 60,
      "api_access": true,
      "diff_enabled": true
    }
  }
}

Usage Stats

Check your current resource usage against plan limits.

GET /api/v1/account/usage

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

Response

200 OK
{
  "data": {
    "monitors": {
      "used": 12,
      "limit": 50,
      "remaining": 38
    },
    "api_keys": {
      "used": 2,
      "limit": 5,
      "remaining": 3
    },
    "snapshots_today": 156
  }
}

Pagination

All list endpoints return paginated results. Use the meta object to navigate through pages.

Query Parameters

NameTypeDefaultDescription
pageinteger1Page number to retrieve
per_pageinteger20Items per page (max 100)

Meta Object

FieldTypeDescription
current_pageintegerCurrent page number
per_pageintegerItems returned per page
totalintegerTotal number of items across all pages
last_pageintegerThe last available page number

Example: Iterating Through Pages

JavaScript
async function getAllMonitors(apiKey) {
  let page = 1;
  let allMonitors = [];

  while (true) {
    const response = await fetch(
      `https://snapshotarchive.com/api/v1/monitors?page=${page}&per_page=100`,
      { headers: { 'Authorization': `Bearer ${apiKey}` } }
    );
    const { data, meta } = await response.json();
    allMonitors.push(...data);

    if (page >= meta.last_page) break;
    page++;
  }

  return allMonitors;
}

Rate Limits

API requests are rate-limited to 60 requests per minute per API key.

Rate limit information is included in every response via headers:

Response Headers
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58

When you exceed the rate limit, you receive a 429 Too Many Requests response:

429 Too Many Requests
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Please retry after 42 seconds.",
    "status": 429
  }
}
Best practice: Implement exponential backoff when you receive a 429 response. Check the Retry-After header for the number of seconds to wait.

Error Codes

The API uses standard HTTP status codes and returns structured error responses.

HTTP Status Codes

CodeMeaningDescription
200OKRequest succeeded
201CreatedResource created successfully
202AcceptedRequest accepted for async processing (e.g., snapshot trigger)
204No ContentResource deleted successfully (no response body)
401UnauthorizedMissing, invalid, or expired API key
403ForbiddenAuthenticated but not authorized for this resource
404Not FoundResource does not exist or file not available
422Validation ErrorRequest data failed validation or plan limit exceeded
429Too Many RequestsRate limit exceeded
500Server ErrorUnexpected server error

Error Response Format

API errors return a structured error object with code, message, and status fields:

API Error
{
  "error": {
    "code": "invalid_api_key",
    "message": "The provided API key is invalid or has been revoked.",
    "status": 401
  }
}

Error Codes Reference

CodeHTTP StatusDescription
missing_api_key401No API key provided in the Authorization header
invalid_api_key401API key does not exist or has been revoked
expired_api_key401API key has expired
monitor_limit_exceeded422Plan monitor limit reached, upgrade required
rate_limit_exceeded429Too many requests in the time window
not_found404Resource or file not found

Validation Errors

Validation failures (422) from Laravel return field-level error details:

422 Validation Error
{
  "message": "The url field is required. (and 1 more error)",
  "errors": {
    "url": ["The url field is required."],
    "frequency_minutes": ["The frequency minutes field must be at least 5."]
  }
}