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 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 Start

Follow these four steps to capture your first screenshot via the API:

Step 1: Check your account & plan

cURL
curl https://snapshotarchive.com/api/v1/account \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"

Verify your plan includes API access (api_access: true) and check your monitor limit.

Step 2: Create a project

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": "My Website"}'

Note the returned id — you'll use it as project_id when creating monitors.

Step 3: Create a monitor

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",
    "project_id": 1,
    "frequency_minutes": 60,
    "viewport_width": 1920,
    "viewport_height": 1080,
    "device_type": "desktop",
    "full_page": true
  }'

The response includes the monitor id. Note: your plan determines the minimum allowed frequency_minutes.

Step 4: Trigger a snapshot

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"
  }
}

The snapshot is captured asynchronously. Poll GET /api/v1/snapshots/{snapshot_id} and check the status field: pendingprocessingcompleted. Once completed, the files object contains download URLs.

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']

Handling Errors

Always check the response status and handle errors gracefully. API errors return a structured error object:

JavaScript
const response = await fetch('https://snapshotarchive.com/api/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: 60 })
});

if (!response.ok) {
  const body = await response.json();

  if (body.error) {
    // API error (401, 403, 429, etc.)
    console.error(`API error [${body.error.code}]: ${body.error.message}`);
  } else if (body.errors) {
    // Validation error (422)
    Object.entries(body.errors).forEach(([field, messages]) => {
      console.error(`${field}: ${messages.join(', ')}`);
    });
  }
  return;
}

const { data } = await response.json();
console.log('Created monitor:', data.id);
PHP (Laravel Http)
$response = Http::withToken('YOUR_API_KEY')
    ->acceptJson()
    ->post('https://snapshotarchive.com/api/v1/monitors', [
        'url' => 'https://example.com',
        'frequency_minutes' => 60,
    ]);

if ($response->failed()) {
    if ($response->json('error')) {
        // API error (401, 403, 429, etc.)
        $code = $response->json('error.code');
        $message = $response->json('error.message');
        throw new \Exception("API error [{$code}]: {$message}");
    }

    if ($response->status() === 422) {
        // Validation errors
        $errors = $response->json('errors');
        // Handle field-level errors...
    }
}

$monitor = $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.

cURL
curl https://snapshotarchive.com/api/v1/projects/1 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"
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}

Soft-delete a project. The project and its data are archived and can be restored by contacting support. Monitors within the project are not affected.

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
disable_animationsbooleanNoDisable CSS animations and transitions before capture. Default: false
hide_selectorsarrayNoArray of CSS selectors to hide before capture (e.g. cookie banners)
click_selectorstringNoCSS selector to click before capture (max 500 chars)
clip_selectorstringNoCSS selector to clip — only capture this element area (max 500 chars)
cookiesarrayNoArray of cookie objects to set before capture (max 20). Each object requires name, value, and domain.
diff_threshold_percentfloatNoMinimum change % to flag as significant (0–100). Default: 0.5
watermark_enabledbooleanNoAdd a timestamp watermark overlay on screenshots. Default: false
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"
    }
  }
}

Cookies Format

When setting cookies, provide an array of objects with name, value, and domain:

Cookies Example
{
  "cookies": [
    { "name": "session_id", "value": "abc123", "domain": "example.com" },
    { "name": "consent", "value": "accepted", "domain": ".example.com" }
  ]
}

Hide Selectors Format

Provide an array of CSS selectors. Matching elements will be hidden (set to visibility: hidden) before capture:

Hide Selectors Example
{
  "hide_selectors": [".cookie-banner", "#popup-overlay", ".chat-widget"]
}
Your plan limits the number of monitors you can create and the minimum capture frequency. If you exceed your monitor limit, the API returns a 422 error with code monitor_limit_exceeded. If the requested frequency is too low for your plan, you receive frequency_not_allowed.
Dashboard-only settings: HTTP Basic Authentication (http_auth_user / http_auth_password) can only be configured through the dashboard for security reasons. These fields appear in monitor responses but cannot be set via API.
GET /api/v1/monitors/{id}

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

cURL
curl https://snapshotarchive.com/api/v1/monitors/1 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"
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}

Soft-delete a monitor. Associated snapshots and diffs are preserved and archived.

cURL
curl -X DELETE https://snapshotarchive.com/api/v1/monitors/1 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"
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.

cURL
curl https://snapshotarchive.com/api/v1/snapshots/9c8b7a6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"

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.
Watermark: If the monitor has watermark_enabled: true, this endpoint returns the image directly as 200 OK with Content-Type: image/png (no redirect) with the URL and capture timestamp overlaid on the image.
GET /api/v1/snapshots/{id}/pdf

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

Starter plan or above required. Free plan users receive a 403 error with code feature_not_available.
cURL
curl -L https://snapshotarchive.com/api/v1/snapshots/{id}/pdf \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -o page.pdf
GET /api/v1/snapshots/{id}/html

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

Starter plan or above required. Free plan users receive a 403 error with code feature_not_available.
cURL
curl -L https://snapshotarchive.com/api/v1/snapshots/{id}/html \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -o source.html.gz
GET /api/v1/snapshots/{id}/package

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

Starter plan or above required. Free plan users receive a 403 error with code feature_not_available.
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.

cURL
curl https://snapshotarchive.com/api/v1/diffs/78 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"

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.

cURL
curl https://snapshotarchive.com/api/v1/account \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"

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": 365,
      "min_frequency_minutes": 30,
      "api_access": true,
      "diff_enabled": true,
      "snapshot_package": true
    }
  }
}
The snapshot_package field indicates whether your plan includes access to PDF, HTML, and snapshot package downloads. If false, requests to /snapshots/{id}/pdf, /html, and /package will return 403.

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.

cURL
curl https://snapshotarchive.com/api/v1/account/usage \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"

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
frequency_not_allowed422Requested capture frequency is below plan minimum
feature_not_available403Feature requires a higher plan (e.g., PDF/HTML capture requires Starter+)
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."]
  }
}

Code Examples

The API works with any language that can make HTTP requests. Below are complete working examples that create a monitor, trigger a snapshot, and retrieve the result.

Any language works. These are just common examples — the API is a standard REST interface. Use your preferred HTTP client in any language: Go, Rust, C#, Swift, Kotlin, etc.

Node.js

JavaScript (Node.js 18+)
const API_KEY = 'YOUR_API_KEY';
const BASE    = 'https://snapshotarchive.com/api/v1';

const headers = {
  'Authorization': `Bearer ${API_KEY}`,
  'Content-Type':  'application/json',
  'Accept':        'application/json',
};

// 1. Create a monitor
const monitor = await fetch(`${BASE}/monitors`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    url: 'https://example.com',
    frequency_minutes: 1440,
    viewport_width: 1920,
    viewport_height: 1080,
    device_type: 'desktop',
    full_page: true,
  }),
}).then(r => r.json());

const monitorId = monitor.data.id;
console.log('Monitor created:', monitorId);

// 2. Trigger a snapshot
const trigger = await fetch(`${BASE}/monitors/${monitorId}/trigger`, {
  method: 'POST',
  headers,
}).then(r => r.json());

const snapshotId = trigger.data.snapshot_id;
console.log('Snapshot queued:', snapshotId);

// 3. Poll until completed
let snapshot;
do {
  await new Promise(r => setTimeout(r, 5000));
  snapshot = await fetch(`${BASE}/snapshots/${snapshotId}`, { headers })
    .then(r => r.json());
} while (snapshot.data.status !== 'completed');

console.log('Screenshot URL:', snapshot.data.files.screenshot_url);

PHP

PHP (Laravel Http / Guzzle)
use Illuminate\Support\Facades\Http;

$api = Http::withToken('YOUR_API_KEY')
    ->acceptJson()
    ->baseUrl('https://snapshotarchive.com/api/v1');

// 1. Create a monitor
$monitor = $api->post('/monitors', [
    'url'               => 'https://example.com',
    'frequency_minutes' => 1440,
    'viewport_width'    => 1920,
    'viewport_height'   => 1080,
    'device_type'       => 'desktop',
    'full_page'         => true,
])->json('data');

// 2. Trigger a snapshot
$trigger = $api->post("/monitors/{$monitor['id']}/trigger")->json('data');
$snapshotId = $trigger['snapshot_id'];

// 3. Poll until completed
do {
    sleep(5);
    $snapshot = $api->get("/snapshots/{$snapshotId}")->json('data');
} while ($snapshot['status'] !== 'completed');

echo $snapshot['files']['screenshot_url'];
PHP (native cURL)
$apiKey  = 'YOUR_API_KEY';
$baseUrl = 'https://snapshotarchive.com/api/v1';

function apiRequest(string $method, string $url, ?array $data = null): array {
    global $apiKey;
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_HTTPHEADER     => [
            "Authorization: Bearer {$apiKey}",
            'Content-Type: application/json',
            'Accept: application/json',
        ],
    ]);
    if ($data) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    }
    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode($response, true);
}

// 1. Create a monitor
$monitor = apiRequest('POST', "{$baseUrl}/monitors", [
    'url'               => 'https://example.com',
    'frequency_minutes' => 1440,
    'device_type'       => 'desktop',
]);

// 2. Trigger a snapshot
$trigger = apiRequest('POST', "{$baseUrl}/monitors/{$monitor['data']['id']}/trigger");
$snapshotId = $trigger['data']['snapshot_id'];

// 3. Poll until completed
do {
    sleep(5);
    $snapshot = apiRequest('GET', "{$baseUrl}/snapshots/{$snapshotId}");
} while ($snapshot['data']['status'] !== 'completed');

echo $snapshot['data']['files']['screenshot_url'];

Python

Python (requests)
import time
import requests

API_KEY  = 'YOUR_API_KEY'
BASE_URL = 'https://snapshotarchive.com/api/v1'
headers  = {
    'Authorization': f'Bearer {API_KEY}',
    'Accept': 'application/json',
}

# 1. Create a monitor
monitor = requests.post(f'{BASE_URL}/monitors', headers=headers, json={
    'url': 'https://example.com',
    'frequency_minutes': 1440,
    'viewport_width': 1920,
    'viewport_height': 1080,
    'device_type': 'desktop',
    'full_page': True,
}).json()['data']

# 2. Trigger a snapshot
trigger = requests.post(
    f"{BASE_URL}/monitors/{monitor['id']}/trigger", headers=headers
).json()['data']

snapshot_id = trigger['snapshot_id']

# 3. Poll until completed
while True:
    time.sleep(5)
    snapshot = requests.get(
        f'{BASE_URL}/snapshots/{snapshot_id}', headers=headers
    ).json()['data']
    if snapshot['status'] == 'completed':
        break

print(snapshot['files']['screenshot_url'])

Ruby

Ruby (net/http)
require 'net/http'
require 'json'
require 'uri'

API_KEY  = 'YOUR_API_KEY'
BASE_URL = 'https://snapshotarchive.com/api/v1'

def api_request(method, path, body = nil)
  uri = URI("#{BASE_URL}#{path}")
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme == 'https'

  request = case method
            when :get    then Net::HTTP::Get.new(uri)
            when :post   then Net::HTTP::Post.new(uri)
            when :put    then Net::HTTP::Put.new(uri)
            when :delete then Net::HTTP::Delete.new(uri)
            end

  request['Authorization'] = "Bearer #{API_KEY}"
  request['Accept']        = 'application/json'
  request['Content-Type']  = 'application/json'
  request.body = body.to_json if body

  JSON.parse(http.request(request).body)
end

# 1. Create a monitor
monitor = api_request(:post, '/monitors', {
  url: 'https://example.com',
  frequency_minutes: 1440,
  device_type: 'desktop'
})

# 2. Trigger a snapshot
trigger = api_request(:post, "/monitors/#{monitor['data']['id']}/trigger")
snapshot_id = trigger['data']['snapshot_id']

# 3. Poll until completed
loop do
  sleep 5
  snapshot = api_request(:get, "/snapshots/#{snapshot_id}")
  break if snapshot['data']['status'] == 'completed'
end

puts snapshot['data']['files']['screenshot_url']

Go

Go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "time"
)

const (
    apiKey  = "YOUR_API_KEY"
    baseURL = "https://snapshotarchive.com/api/v1"
)

func apiRequest(method, path string, body any) map[string]any {
    var reqBody *bytes.Reader
    if body != nil {
        b, _ := json.Marshal(body)
        reqBody = bytes.NewReader(b)
    } else {
        reqBody = bytes.NewReader(nil)
    }

    req, _ := http.NewRequest(method, baseURL+path, reqBody)
    req.Header.Set("Authorization", "Bearer "+apiKey)
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Accept", "application/json")

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    var result map[string]any
    json.NewDecoder(resp.Body).Decode(&result)
    return result
}

func main() {
    // 1. Create a monitor
    monitor := apiRequest("POST", "/monitors", map[string]any{
        "url":               "https://example.com",
        "frequency_minutes": 1440,
        "device_type":       "desktop",
    })
    monitorID := monitor["data"].(map[string]any)["id"]

    // 2. Trigger a snapshot
    trigger := apiRequest("POST", fmt.Sprintf("/monitors/%v/trigger", monitorID), nil)
    snapshotID := trigger["data"].(map[string]any)["snapshot_id"]

    // 3. Poll until completed
    for {
        time.Sleep(5 * time.Second)
        snap := apiRequest("GET", fmt.Sprintf("/snapshots/%v", snapshotID), nil)
        data := snap["data"].(map[string]any)
        if data["status"] == "completed" {
            files := data["files"].(map[string]any)
            fmt.Println(files["screenshot_url"])
            break
        }
    }
}