Menu
Docs/Screenshot Diff

Screenshot Diff

Compare two screenshots pixel-by-pixel and get back the percentage of changed pixels, a pixel count, and a visual diff image with changed regions highlighted in red.

There are two modes:

  1. URL mode — pass url_a and url_b; SnapSharp captures both for you and compares them.
  2. Image mode — pass image_a and image_b as base64-encoded PNGs and SnapSharp compares them directly (no captures).
POST/v1/diff

Screenshot Diff requires a Growth plan or higher. Free and Starter requests return 403 plan_required.

Parameters

Exactly one mode is required. You must send either (url_a + url_b) or (image_a + image_b). Mixing the two is not supported and returns 400 validation_error.

ParameterTypeDefaultDescription

Image mode tip. image_a / image_b must be valid PNG bytes, base64-encoded. Both images must have the same dimensions — otherwise the API returns 400 validation_error with the mismatch message from the comparator.

Response

Returns JSON with the diff metrics and a base64-encoded visual diff image.

{
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "diff_percent": 12.45,
  "diff_pixels": 114892,
  "total_pixels": 921600,
  "width": 1280,
  "height": 720,
  "threshold": 0.1,
  "diff_image_base64": "iVBORw0KGgo..."
}
FieldTypeDescription
request_idstringUUID of this request (also in X-Request-Id header)
diff_percentnumberPercentage of pixels that differ (0–100)
diff_pixelsnumberAbsolute count of differing pixels
total_pixelsnumberTotal pixels compared (width × height)
widthnumberWidth of the compared images in pixels
heightnumberHeight of the compared images in pixels
thresholdnumberThe threshold value used for this comparison
diff_image_base64stringPNG diff image as base64 — changed pixels highlighted in red

AI-labeled response

When you call with describe_changes: true and have a default AI provider configured, the response also includes changes, ai_template, ai_provider, and ai_model:

{
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "diff_percent": 12.45,
  "diff_pixels": 114892,
  "total_pixels": 921600,
  "width": 1280,
  "height": 720,
  "threshold": 0.1,
  "diff_image_base64": "iVBORw0KGgo...",
  "changes": {
    "summary": "Hero headline copy and CTA button color changed",
    "regions": [
      { "area": "hero", "change": "Headline text updated" },
      { "area": "cta-primary", "change": "Background color changed from blue to green" }
    ]
  },
  "ai_template": { "id": "…", "name": "Visual Diff Labeler" },
  "ai_provider": "anthropic",
  "ai_model": "claude-3-5-sonnet-20241022"
}

If describe_changes: true is set but no default provider is configured (or the AI call fails), the pixel-diff fields are still returned and changes is null with an ai_error string explaining what happened. The request is never failed over a flaky AI vendor.

FieldTypeDescription
changesobject | nullStructured AI description (JSON). null if AI is disabled or failed.
ai_errorstringPresent only when changes is null and AI was requested. Human-readable hint.
ai_templateobject{ id, name } of the prompt template used
ai_providerenumanthropic / openai / openrouter / custom
ai_modelstringModel identifier from your provider config

Examples

Compare two URLs (curl)

curl -X POST "https://api.snapsharp.dev/v1/diff" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url_a": "https://example.com",
    "url_b": "https://staging.example.com",
    "width": 1440,
    "height": 900,
    "threshold": 0.05
  }'

Compare two URLs (Node.js)

const res = await fetch('https://api.snapsharp.dev/v1/diff', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer sk_live_YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url_a: 'https://example.com',
    url_b: 'https://staging.example.com',
    width: 1440,
    threshold: 0.05,
  }),
});

const { diff_percent, diff_pixels, diff_image_base64 } = await res.json();

console.log(`Changed: ${diff_percent.toFixed(2)}% (${diff_pixels} pixels)`);

// Persist the visual diff for review
require('fs').writeFileSync('diff.png', Buffer.from(diff_image_base64, 'base64'));

Compare two base64 images (curl)

Useful when you already have both PNGs on your side (e.g. stored snapshots in CI).

IMAGE_A=$(base64 -w0 before.png)
IMAGE_B=$(base64 -w0 after.png)

curl -X POST "https://api.snapsharp.dev/v1/diff" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"image_a\": \"$IMAGE_A\",
    \"image_b\": \"$IMAGE_B\",
    \"threshold\": 0.1
  }"

Compare two base64 images (Node.js)

import { readFileSync, writeFileSync } from 'node:fs';

const imageA = readFileSync('before.png').toString('base64');
const imageB = readFileSync('after.png').toString('base64');

const res = await fetch('https://api.snapsharp.dev/v1/diff', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer sk_live_YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    image_a: imageA,
    image_b: imageB,
    threshold: 0.1,
  }),
});

const data = await res.json();
writeFileSync('diff.png', Buffer.from(data.diff_image_base64, 'base64'));

if (data.diff_percent > 1) {
  console.error(`Visual regression: ${data.diff_percent.toFixed(2)}% changed`);
  process.exit(1);
}

AI-labeled diff (curl)

Requires a default AI provider at settings/ai (BYOK — bring your own Anthropic / OpenAI / OpenRouter key).

curl -X POST "https://api.snapsharp.dev/v1/diff" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url_a": "https://example.com",
    "url_b": "https://staging.example.com",
    "describe_changes": true
  }'

Use cases

  • Visual regression testing — compare production vs staging before each deploy
  • A/B testing — quantify visual differences between variants
  • Brand monitoring — detect unauthorized changes to your public pages
  • QA automation — fail a CI job when diff_percent crosses a budget

Errors

StatusErrorWhen
400validation_errorNeither (url_a + url_b) nor (image_a + image_b) was provided, or image dimensions mismatch, or body fails Zod validation (e.g. invalid URL, out-of-range threshold)
401unauthorizedMissing or invalid API key
403ip_not_allowedAPI key has an IP whitelist and your IP is not on it
403plan_requiredCurrent plan is below Growth (required_plan: "growth" in body)
429rate_limit_exceededToo many requests per minute for your plan
500internal_errorCapture or comparison failure
Screenshot Diff