If you run a web agency, do freelance development, or manage websites for clients, you know the drill: every week or month, you need to show clients what their site looks like, prove that changes went live, or document the state of a redesign. Manually taking screenshots, cropping them, and pasting them into a PDF is tedious work that eats hours.
A website screenshot API can eliminate that entirely. Instead of opening browsers and hitting Print Screen, you make an API call and get back a pixel-perfect image. String a few calls together with some templating, and you have an automated reporting pipeline that runs itself.
This guide walks through building exactly that, with working code you can drop into your stack today.
Text-only reports leave room for misunderstanding. When you tell a client "the hero section has been updated," they might picture something completely different from what shipped. A screenshot removes all ambiguity.
Common use cases for screenshot-based reporting:
The problem is doing this manually. Open a browser, navigate, screenshot, crop, save, repeat for every URL and viewport. For 10 client sites with 5 pages each, that is 50 screenshots before you even start assembling the report.
Let's start with the simplest possible screenshot. Using GrabShot's screenshot API, you can capture any URL with a single HTTP request:
curl "https://grabshot.dev/api/screenshot?url=https://example.com&width=1440&height=900&format=png" \
-H "X-API-Key: YOUR_API_KEY" \
--output homepage.png
const fs = require('fs');
async function captureScreenshot(url, filename) {
const params = new URLSearchParams({
url,
width: '1440',
height: '900',
format: 'png'
});
const res = await fetch(`https://grabshot.dev/api/screenshot?${params}`, {
headers: { 'X-API-Key': process.env.GRABSHOT_API_KEY }
});
const buffer = Buffer.from(await res.arrayBuffer());
fs.writeFileSync(filename, buffer);
console.log(`Saved ${filename} (${buffer.length} bytes)`);
}
captureScreenshot('https://example.com', 'homepage.png');
import requests
def capture_screenshot(url, filename):
response = requests.get(
'https://grabshot.dev/api/screenshot',
params={
'url': url,
'width': 1440,
'height': 900,
'format': 'png'
},
headers={'X-API-Key': 'YOUR_API_KEY'}
)
with open(filename, 'wb') as f:
f.write(response.content)
print(f'Saved {filename} ({len(response.content)} bytes)')
capture_screenshot('https://example.com', 'homepage.png')
That gets you a single screenshot. Now let's build a full reporting system around it.
A practical client report needs screenshots of multiple pages, at multiple viewport sizes, assembled into a single document. Here is a Node.js script that does all of that:
const fs = require('fs');
const path = require('path');
const API_BASE = 'https://grabshot.dev/api/screenshot';
const API_KEY = process.env.GRABSHOT_API_KEY;
// Define your clients and their pages
const clients = [
{
name: 'Acme Corp',
pages: [
{ label: 'Homepage', url: 'https://acmecorp.com' },
{ label: 'Pricing', url: 'https://acmecorp.com/pricing' },
{ label: 'Contact', url: 'https://acmecorp.com/contact' }
]
},
{
name: 'StartupXYZ',
pages: [
{ label: 'Landing Page', url: 'https://startupxyz.io' },
{ label: 'Dashboard', url: 'https://app.startupxyz.io/demo' }
]
}
];
const viewports = [
{ label: 'Desktop', width: 1440, height: 900 },
{ label: 'Mobile', width: 390, height: 844 }
];
async function captureForClient(client) {
const date = new Date().toISOString().split('T')[0];
const dir = path.join('reports', client.name.toLowerCase().replace(/\s+/g, '-'), date);
fs.mkdirSync(dir, { recursive: true });
const captures = [];
for (const page of client.pages) {
for (const vp of viewports) {
const filename = `${page.label.toLowerCase().replace(/\s+/g, '-')}-${vp.label.toLowerCase()}.png`;
const filepath = path.join(dir, filename);
const params = new URLSearchParams({
url: page.url,
width: String(vp.width),
height: String(vp.height),
format: 'png',
full_page: 'false'
});
const res = await fetch(`${API_BASE}?${params}`, {
headers: { 'X-API-Key': API_KEY }
});
const buffer = Buffer.from(await res.arrayBuffer());
fs.writeFileSync(filepath, buffer);
captures.push({ page: page.label, viewport: vp.label, file: filename });
console.log(` Captured: ${page.label} (${vp.label})`);
// Small delay to respect rate limits
await new Promise(r => setTimeout(r, 500));
}
}
return { client: client.name, date, dir, captures };
}
async function generateAllReports() {
console.log('Starting report generation...\n');
for (const client of clients) {
console.log(`\n${client.name}:`);
const result = await captureForClient(client);
console.log(` Done! ${result.captures.length} screenshots saved to ${result.dir}`);
}
}
generateAllReports();
Run this on a schedule (cron job, GitHub Action, or a task scheduler), and you have fresh screenshots of every client site waiting for you each morning.
Screenshots in a folder are useful but not client-ready. Let's add a function that assembles them into a clean HTML report you can email or convert to PDF:
function generateHTMLReport(result) {
const { client, date, captures, dir } = result;
const html = `<!DOCTYPE html>
<html>
<head>
<title>${client} - Website Report ${date}</title>
<style>
body { font-family: sans-serif; max-width: 900px; margin: 0 auto; padding: 2rem; }
h1 { border-bottom: 2px solid #4361ee; padding-bottom: 0.5rem; }
.capture { margin: 2rem 0; page-break-inside: avoid; }
.capture img { max-width: 100%; border: 1px solid #ddd; border-radius: 4px; }
.capture h3 { margin-bottom: 0.5rem; }
.meta { color: #666; font-size: 0.9rem; }
</style>
</head>
<body>
<h1>${client} - Website Report</h1>
<p class="meta">Generated: ${date}</p>
${captures.map(c => `
<div class="capture">
<h3>${c.page} (${c.viewport})</h3>
<img src="${c.file}" alt="${c.page} ${c.viewport}" />
</div>`).join('')}
</body>
</html>`;
const reportPath = path.join(dir, 'report.html');
fs.writeFileSync(reportPath, html);
console.log(` Report: ${reportPath}`);
}
Want a PDF instead? You can convert the HTML report using GrabShot's sister tool, PDFMagic, or pipe it through any HTML-to-PDF converter.
Here is the same concept in Python, with email delivery built in using SMTP:
import requests
import os
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.mime.text import MIMEText
from datetime import date
API_KEY = os.environ['GRABSHOT_API_KEY']
def capture_pages(pages, viewport_width=1440):
"""Capture screenshots for a list of URLs."""
screenshots = []
for label, url in pages:
resp = requests.get(
'https://grabshot.dev/api/screenshot',
params={'url': url, 'width': viewport_width, 'height': 900, 'format': 'png'},
headers={'X-API-Key': API_KEY}
)
screenshots.append((label, resp.content))
return screenshots
def build_email(client_name, screenshots):
"""Build an email with embedded screenshots."""
msg = MIMEMultipart('related')
msg['Subject'] = f'{client_name} - Website Report {date.today()}'
html_parts = [f'<h2>{label}</h2><img src="cid:{i}" style="max-width:100%"/>'
for i, (label, _) in enumerate(screenshots)]
html = MIMEText(f'<html><body>{"".join(html_parts)}</body></html>', 'html')
msg.attach(html)
for i, (label, img_data) in enumerate(screenshots):
img = MIMEImage(img_data, name=f'{label}.png')
img.add_header('Content-ID', f'<{i}>')
msg.attach(img)
return msg
# Usage
pages = [
('Homepage', 'https://example.com'),
('Blog', 'https://example.com/blog'),
('Pricing', 'https://example.com/pricing'),
]
screenshots = capture_pages(pages)
email = build_email('Example Client', screenshots)
# Send via your SMTP server
GrabShot's screenshot API gives you 25 free screenshots per month. No credit card required.
Try It Free →The real power of automated reporting is the "automated" part. Set up a cron job to run your script weekly:
# Every Monday at 8am, generate client reports
0 8 * * 1 cd /opt/reports && node generate-reports.js >> /var/log/reports.log 2>&1
Or use a GitHub Action if your code lives in a repo:
name: Weekly Client Reports
on:
schedule:
- cron: '0 8 * * 1' # Monday 8am UTC
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: node generate-reports.js
env:
GRABSHOT_API_KEY: ${{ secrets.GRABSHOT_API_KEY }}
- uses: actions/upload-artifact@v4
with:
name: client-reports
path: reports/
A few practical tips from experience:
One of the most valuable things you can do with automated screenshots is track changes. If you capture the same pages weekly, you build a visual history of the site. Pair this with GrabShot's screenshot comparison feature to automatically detect visual differences between captures.
This is especially useful for:
Automated client reports with screenshots save real time. Instead of spending an hour per client on manual screenshots and document assembly, you spend 10 minutes setting up a script that runs forever. Your clients get consistent, professional reports, and you get that hour back every week.
The code above works as-is. Grab an API key from GrabShot, plug in your client URLs, and schedule it. The free tier gives you 25 screenshots per month, which is enough to test the workflow before scaling up.