DEV Community

GrabShot
GrabShot

Posted on

How to Capture Website Screenshots with a Simple API Call

If you've ever needed to capture website screenshots programmatically, you know the pain: spinning up Puppeteer, managing Chrome instances, handling timeouts, dealing with memory leaks. It's a whole infrastructure problem for what should be a simple task.

I built GrabShot to solve this. One API call, get a screenshot. Here's how it works.

The Simple Version

curl "https://grabshot.dev/v1/screenshot?url=https://github.com&format=png" \
  -H "x-api-key: YOUR_KEY" \
  -o github.png
Enter fullscreen mode Exit fullscreen mode

That's it. You get back a PNG of github.com. No Puppeteer setup, no Chrome management, no memory leaks to debug at 3 AM.

Getting Started (Free Tier)

  1. Head to grabshot.dev/dashboard
  2. Create an account (just email)
  3. You get 25 free screenshots per month
  4. Start capturing

What Can You Actually Do With This?

OG Images for Your Blog

Generate dynamic Open Graph images so your links look great when shared:

const ogUrl = `https://grabshot.dev/v1/screenshot?url=${encodeURIComponent(myPageUrl)}&width=1200&height=630&format=png`;
Enter fullscreen mode Exit fullscreen mode

Automated Testing

Capture screenshots of your staging environment before and after deploys:

const screenshot = await fetch(
  `https://grabshot.dev/v1/screenshot?url=${stagingUrl}&width=1440&height=900`,
  { headers: { 'x-api-key': process.env.GRABSHOT_KEY } }
);
const buffer = await screenshot.arrayBuffer();
fs.writeFileSync(`screenshots/${Date.now()}.png`, Buffer.from(buffer));
Enter fullscreen mode Exit fullscreen mode

Link Previews

Build rich link previews for your chat app or CMS:

async function getLinkPreview(url) {
  const [meta, screenshot] = await Promise.all([
    fetch(`https://metapeek.grabshot.dev/v1/extract?url=${encodeURIComponent(url)}`).then(r => r.json()),
    fetch(`https://grabshot.dev/v1/screenshot?url=${encodeURIComponent(url)}&width=800&height=600`, {
      headers: { 'x-api-key': API_KEY }
    }).then(r => r.blob())
  ]);
  return { title: meta.title, description: meta.description, image: screenshot };
}
Enter fullscreen mode Exit fullscreen mode

Device Frames

Want the screenshot wrapped in a browser or phone frame? Add &frame=browser or &frame=iphone:

https://grabshot.dev/v1/screenshot?url=https://your-site.com&frame=browser&width=1440
Enter fullscreen mode Exit fullscreen mode

API Parameters

Parameter Description Default
url Target URL (required) -
width Viewport width in pixels 1440
height Viewport height in pixels 900
format png, jpeg, or webp png
quality JPEG/WebP quality (1-100) 80
frame Device frame: browser, iphone, ipad none
full_page Capture full scrollable page false
delay Wait ms before capture 0
dark_mode Force dark mode false

Pricing

The free tier gives you 25 screenshots/month with a small watermark. Paid plans start at $9/month for 2,500 screenshots with no watermark, AI cleanup, and priority rendering.

Full pricing: grabshot.dev/#pricing

Node.js Quick Start

const GRABSHOT_KEY = process.env.GRABSHOT_KEY;

async function screenshot(url, options = {}) {
  const params = new URLSearchParams({
    url,
    width: options.width || 1440,
    height: options.height || 900,
    format: options.format || 'png',
    ...options
  });

  const res = await fetch(`https://grabshot.dev/v1/screenshot?${params}`, {
    headers: { 'x-api-key': GRABSHOT_KEY }
  });

  if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
  return Buffer.from(await res.arrayBuffer());
}

// Usage
const img = await screenshot('https://example.com', { 
  width: 1200, 
  frame: 'browser' 
});
fs.writeFileSync('example.png', img);
Enter fullscreen mode Exit fullscreen mode

Python Quick Start

import requests

def screenshot(url, api_key, **kwargs):
    params = {"url": url, **kwargs}
    headers = {"x-api-key": api_key}
    r = requests.get("https://grabshot.dev/v1/screenshot", params=params, headers=headers)
    r.raise_for_status()
    return r.content

# Usage
img = screenshot("https://example.com", "YOUR_KEY", width=1200, format="png")
with open("example.png", "wb") as f:
    f.write(img)
Enter fullscreen mode Exit fullscreen mode

When to Use an API vs. Self-Hosting Puppeteer

Use an API when:

  • You need screenshots occasionally (not millions/day)
  • You don't want to manage Chrome/Puppeteer infrastructure
  • You need reliable rendering across different sites
  • You're building a feature, not a screenshot service

Self-host when:

  • You need >100k screenshots/day
  • You have specific browser extension requirements
  • Latency under 500ms is critical
  • You're building a screenshot service yourself

For most use cases, an API is the right call. You're paying $9-29/month instead of spending days on infrastructure that breaks when Chrome updates.


GrabShot is free to try. 25 screenshots/month, no credit card required. If you build something cool with it, I'd love to hear about it.

Top comments (0)