Common Automation Patterns
- Competitor monitoring — screenshot competitor pricing pages daily, detect changes
- Visual regression in CI — screenshot on every deploy, diff against baseline
- Uptime proof — capture timestamps + screenshots for SLA documentation
- Content archiving — daily screenshots of news/articles for legal/compliance
- OG image generation — auto-capture page thumbnail on blog post publish
- Thumbnail generation — capture user-submitted URLs for link previews
Scheduled Capture with node-cron
npm install node-cron node-fetch
import cron from 'node-cron';
import { writeFile } from 'fs/promises';
import { join } from 'path';
const URLS = [
{ name: 'competitor-pricing', url: 'https://competitor.com/pricing' },
{ name: 'our-homepage', url: 'https://mysite.com' },
{ name: 'status-page', url: 'https://status.myservice.com' },
];
async function captureURL(name, url) {
const res = await fetch('https://api.snapapi.pics/v1/screenshot', {
method: 'POST',
headers: { 'X-Api-Key': process.env.SNAPAPI_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ url, fullPage: true, format: 'png', blockAds: true }),
});
const date = new Date().toISOString().split('T')[0];
const filename = `${name}-${date}.png`;
await writeFile(join('./captures', filename), Buffer.from(await res.arrayBuffer()));
console.log(`✓ Captured ${name} → ${filename}`);
}
// Run every day at 9 AM
cron.schedule('0 9 * * *', async () => {
console.log('Starting daily capture run...');
for (const { name, url } of URLS) {
await captureURL(name, url);
}
}, {
timezone: 'America/New_York',
});
Pixel-Diff Change Detection
Detect when a monitored page changes by comparing screenshots with pixelmatch:
npm install pixelmatch pngjs
import pixelmatch from 'pixelmatch';
import { PNG } from 'pngjs';
import { readFile, writeFile, access } from 'fs/promises';
async function detectChange(name, newBuffer) {
const baselinePath = `./baselines/${name}.png`;
// If no baseline, save current as baseline
try { await access(baselinePath); } catch {
await writeFile(baselinePath, newBuffer);
console.log(`✓ Baseline saved for ${name}`);
return { changed: false, isFirstRun: true };
}
const baseline = PNG.sync.read(await readFile(baselinePath));
const current = PNG.sync.read(newBuffer);
const { width, height } = baseline;
const diff = new PNG({ width, height });
const mismatch = pixelmatch(baseline.data, current.data, diff.data, width, height, { threshold: 0.1 });
const changePercent = (mismatch / (width * height)) * 100;
if (changePercent > 0.5) { // >0.5% pixels changed
await writeFile(`./diffs/${name}-diff.png`, PNG.sync.write(diff));
await writeFile(baselinePath, newBuffer); // update baseline
return { changed: true, changePercent: changePercent.toFixed(2) };
}
return { changed: false, changePercent: changePercent.toFixed(2) };
}
// Alert via Slack webhook
async function alertSlack(name, changePercent) {
await fetch(process.env.SLACK_WEBHOOK, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `🔔 *Page change detected: ${name}*\n${changePercent}% of pixels changed`,
}),
});
}
GitHub Actions Scheduled Capture
GitHub Actions cron jobs are perfect for visual monitoring — free for public repos, and artifacts give you a timestamped archive:
# .github/workflows/capture.yml
name: Daily Page Capture
on:
schedule:
- cron: '0 9 * * *' # 9 AM UTC daily
workflow_dispatch: # manual trigger
jobs:
capture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- name: Capture screenshots
env:
SNAPAPI_KEY: ${{ secrets.SNAPAPI_KEY }}
run: |
mkdir -p captures
node scripts/capture.js
- name: Upload captures as artifact
uses: actions/upload-artifact@v4
with:
name: captures-${{ github.run_number }}
path: captures/
retention-days: 30 # keep 30 days of history
- name: Check for changes and notify
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
run: node scripts/diff-and-notify.js
GitHub Actions cron has a minimum interval of 5 minutes. For tighter monitoring, use a self-hosted runner or AWS EventBridge + Lambda.
AWS Lambda + EventBridge Scheduled Capture
// lambda/capture-handler.js
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
const s3 = new S3Client({ region: 'us-east-1' });
export const handler = async (event) => {
const urls = [
{ name: 'pricing', url: 'https://competitor.com/pricing' },
{ name: 'homepage', url: 'https://mysite.com' },
];
const date = new Date().toISOString().split('T')[0];
const results = [];
for (const { name, url } of urls) {
const res = await fetch('https://api.snapapi.pics/v1/screenshot', {
method: 'POST',
headers: { 'X-Api-Key': process.env.SNAPAPI_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ url, fullPage: true, format: 'png' }),
});
const buffer = Buffer.from(await res.arrayBuffer());
await s3.send(new PutObjectCommand({
Bucket: process.env.BUCKET,
Key: `captures/${date}/${name}.png`,
Body: buffer,
ContentType: 'image/png',
}));
results.push({ name, status: 'captured' });
}
return { statusCode: 200, body: JSON.stringify(results) };
};
# serverless.yml (Serverless Framework)
functions:
capture:
handler: lambda/capture-handler.handler
timeout: 60
events:
- schedule: rate(1 day) # or: cron(0 9 * * ? *)
environment:
SNAPAPI_KEY: ${env:SNAPAPI_KEY}
BUCKET: my-captures-bucket
No-Code Automation with n8n
n8n's HTTP Request node can call SnapAPI directly — no code required:
- Add a Schedule trigger (e.g. "Every day at 9 AM")
- Add an HTTP Request node:
- Method:
POST
- URL:
https://api.snapapi.pics/v1/screenshot
- Headers:
X-Api-Key: your_key, Content-Type: application/json
- Body:
{"url": "https://example.com", "fullPage": true, "format": "png"}
- Add a Move Binary Data node to handle the PNG response
- Add an S3 or Google Drive node to store the capture
- Add a Slack node to notify on completion
Make (Integromat) Scenario
Same pattern in Make: Scheduler → HTTP → Google Drive/Dropbox → Slack/Email. SnapAPI's REST API connects natively to any Make HTTP module.
Queue-Based Bulk Capture
For high-volume capture (hundreds of URLs), use a queue to control concurrency and retries:
import PQueue from 'p-queue';
const queue = new PQueue({ concurrency: 5 }); // 5 parallel captures
const urls = [/* hundreds of URLs */];
const results = await Promise.all(
urls.map((url, i) =>
queue.add(async () => {
try {
const res = await fetch('https://api.snapapi.pics/v1/screenshot', {
method: 'POST',
headers: { 'X-Api-Key': process.env.SNAPAPI_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ url, format: 'png', fullPage: true }),
});
const buffer = Buffer.from(await res.arrayBuffer());
await writeFile(`captures/${i}.png`, buffer);
return { url, status: 'ok' };
} catch (err) {
return { url, status: 'error', error: err.message };
}
})
)
);
console.log(`Done: ${results.filter(r => r.status === 'ok').length} captures`);
console.log(`Failed: ${results.filter(r => r.status === 'error').length}`);