Website Thumbnail API for Directories and Catalogs
A SaaS directory with 500 listed tools and no thumbnails is a spreadsheet with extra steps. Users scroll past text-only listings because there's nothing to stop their eye. Add a visual preview of each website and the same directory becomes browsable. Click-through rates climb, time on page doubles, and the directory starts feeling like a product instead of a database dump.
The hard part isn't displaying thumbnails. It's generating them. Open Graph tags cover maybe 60% of sites, and half of those serve a generic company logo that looks identical across every listing. The rest return a 404, a placeholder square, or nothing at all. Try scraping og:image from a list of 500 SaaS tools and see how many come back with an actual page preview versus a 200×200 company icon on a white background. A wall of identical logos isn't a visual directory. It's a worse version of the spreadsheet.
Taking actual screenshots of each website solves this. A website thumbnail API captures what the visitor would see, not what a developer remembered to put in a meta tag two years ago. The question is how to do it at scale without running your own fleet of headless browsers.
Why self-hosted Puppeteer breaks down for directories
Running Puppeteer or Playwright on your own server works for 20 URLs. At 500, the math changes. Each headless Chromium instance eats 200-400 MB of RAM. On a 2 GB VPS, that's four to six concurrent captures before the server starts swapping to disk. At that point, everything slows down, screenshots start timing out, and the occasional zombie Chrome process accumulates until you restart the whole machine.
A developer on Medium documented spending over 40 hours making Puppeteer production-ready for a thumbnail pipeline: leak detection, process cleanup, retry logic, proxy rotation, and browser version management. That's a week of engineering time before the first clean thumbnail ships. And the maintenance never stops. Chrome updates can silently break selectors, memory leaks creep back, and sites that worked yesterday start blocking your datacenter IPs today.
For teams where thumbnails are the product, that investment makes sense. For everyone else, a screenshot API replaces the infrastructure with a single HTTP call.
Generate a website thumbnail with one API call
The screenshotrun API captures a URL in a real Chromium browser and returns the image. For thumbnails, the key is the resize_width parameter: the API renders at full desktop resolution (1280x960) so text and layout stay sharp, then scales the output down to your target thumbnail size. One request, grid-ready image, no post-processing pipeline.
curl -X GET "https://screenshotrun.com/api/v1/screenshots/capture?url=https://linear.app&width=1280&height=960&format=webp&quality=80&resize_width=400&block_cookies=true&block_ads=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
--output linear-thumbnail.webp
That request captures Linear's homepage at 1280x960, blocks cookie banners and ads, and delivers a 400px-wide WebP thumbnail. The output is typically 25-35 KB, small enough to load 50 thumbnails in a grid without slowing down the page. Compare that to a raw 1280px PNG screenshot at 1.5-2 MB. The resize and format conversion happen server-side, so your application never touches image processing.
Picking the right thumbnail dimensions for your layout
The viewport you capture at determines what the thumbnail shows. Capture at 375px wide and you get the mobile layout: hamburger menu, stacked blocks, no sidebar. That rarely makes a useful thumbnail for a desktop directory grid. Capture at 1280x960 to get the standard desktop view, then use resize_width to output the exact pixel size your card layout needs.
| Context | Recommended capture | resize_width | Typical file size (WebP) |
|---|---|---|---|
| Card grid (small) | 1280 × 960 | 320 | ~18 KB |
| Card grid (medium) | 1280 × 960 | 400 | ~28 KB |
| Dashboard wide card | 1280 × 800 | 480 | ~35 KB |
| List view (compact) | 1280 × 960 | 200 | ~10 KB |
| Hero/detail preview | 1440 × 900 | 800 | ~65 KB |
The 4:3 aspect ratio (1280×960) works best for thumbnail grids because the slightly taller frame captures more content below the fold. The custom viewport page covers the full range of viewport options, including mobile and tablet presets if your directory shows device-specific previews.
Clean thumbnails: blocking banners, ads, and chat widgets
A thumbnail is a small image. When a cookie consent banner covers 40% of a full-size screenshot, it's just a banner with some content peeking around the edges. At 400px wide, the consent text becomes illegible smudge that makes the thumbnail useless. Same goes for interstitial ads, sticky headers that take up a third of the viewport, and live chat bubbles sitting in the corner.
screenshotrun blocks these automatically with dedicated parameters. block_cookies removes consent banners, block_ads strips ad overlays, and block_chats hides live chat widgets. For anything else, hide_selectors[] targets arbitrary elements by CSS selector. A notification bar pinned to the top of the page? Pass hide_selectors[]=.notification-bar and it's gone from the capture.
The difference is visible. Two thumbnails of the same European news site: one with the consent banner filling the top half, one clean. The clean version actually shows the content. For a directory where users scan 20-30 thumbnails at a glance, that clarity is the difference between a useful preview and visual noise. The cookie banner blocking page shows more examples of before-and-after captures.
Handling SPAs, lazy-loaded content, and slow sites
Modern web apps don't render on the server. A React dashboard, a Vue marketing page, a Next.js product landing, they all ship an empty HTML shell and fill it with content after JavaScript runs and API calls finish. Capture too early and the thumbnail shows a loading spinner or a blank white rectangle.
The delay parameter adds a fixed wait after the page loads. Two to three seconds handles most marketing sites. But for apps that fetch data from external APIs with unpredictable response times, a fixed delay is either too short (blank thumbnail) or too long (wasted time on every capture).
wait_for_selector fixes this. It pauses the capture until a specific CSS selector appears in the DOM:
curl -X GET "https://screenshotrun.com/api/v1/screenshots/capture?url=https://app.example.com&wait_for_selector=.product-grid&format=webp&resize_width=400&block_cookies=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
--output app-thumbnail.webp
The API waits until .product-grid exists in the DOM, then captures immediately. If the element appears in 600ms, you get the result in under a second. If it never appears within the timeout window, the API captures whatever is on screen rather than hanging forever.
For a directory of user-submitted URLs where you can't predict each page's DOM structure, delay: 3 is the pragmatic default. Save wait_for_selector for pages you control or pages you've tested individually.
Who uses a website thumbnail API
The use cases converge on one pattern: any application where users browse a collection of websites and need a visual preview to decide which one to click.
SaaS directories and catalogs. Product Hunt, G2, Capterra, and hundreds of indie alternatives list software tools with a visual preview of each product's interface. Every new listing needs a fresh thumbnail. At 5,000+ listings, batch generation and periodic refresh become engineering problems that an API solves in the background.
Bookmark managers. Tools like Raindrop.io and open-source alternatives like Linkwarden save a visual snapshot of every bookmarked page. The thumbnail helps users recognize saved pages at a glance, especially in grid view where titles alone aren't enough. High volume, varied URLs, many behind cookie banners.
Template marketplaces. ThemeForest, Framer Marketplace, Envato Elements — every theme and template needs a live preview thumbnail that shows the actual rendered design, not a static mockup. Consistency matters here: uniform viewport sizes, no cookie banners, identical quality settings across hundreds of entries.
Portfolio sites. Agencies and freelancers displaying client work as a visual grid. The thumbnails need to look polished, load fast, and refresh periodically as client sites evolve. A stale thumbnail showing last year's design undermines the portfolio.
SEO and competitive analysis tools. Audit reports that include a visual snapshot of each analyzed page. The thumbnail provides instant context, letting the user see what the page looks like before diving into the data.
Image format and quality for thumbnail grids
When a page loads 30 thumbnails at once, file size compounds. Thirty 1.5 MB PNGs mean 45 MB of images on a single page view. That's a performance disaster on mobile and an unnecessary bandwidth cost on desktop. The format and quality parameters control this directly.
WebP at quality 80 is the sweet spot for most thumbnail grids. It delivers 25-35% smaller files than JPEG at equivalent visual quality, and browser support is above 95%. A 400px-wide WebP thumbnail typically lands between 20-35 KB.
AVIF cuts another 20-30% off WebP file sizes, but encoding is slower and browser support sits around 93%. Worth considering for static directories that generate thumbnails once and serve them for weeks, where encoding speed doesn't matter. For directories that refresh frequently, WebP's faster encoding wins.
JPEG remains the safe fallback for maximum compatibility. Quality 80 balances clarity and file size. For directories that don't control the client browser, JPEG at 80 is the no-surprises choice.
PNG makes sense only if you need transparency, like thumbnails displayed against a non-white background using the omit_background parameter. For standard thumbnails, PNG files run 3-5x larger than WebP with no visual benefit.
Caching strategy: balancing freshness and cost
Not every thumbnail needs to be fresh every day. A template marketplace where listings change once a month doesn't need daily refreshes. A competitive analysis dashboard tracking pricing pages might need weekly updates. The cache_ttl parameter controls how long the API caches a result before re-rendering.
| Directory type | Recommended cache_ttl | Refresh cycle |
|---|---|---|
| Template marketplace | 604800 (7 days) | Weekly background job |
| SaaS directory | 259200 (3 days) | Twice-weekly batch |
| Bookmark manager | 86400 (1 day) | On-demand with daily sweep |
| Competitive monitoring | 0 (no cache) | Every capture is fresh |
When the same URL is requested within the TTL window with the same parameters, the API returns the cached result instantly. No re-rendering, no credit spent. For a 500-listing directory on a 7-day cache, only ~70 thumbnails per day actually consume API credits (assuming roughly equal distribution of cache expiry). That's well within the free tier of 200 screenshots per month.
The pattern I use: set a generous TTL in the API request, store the downloaded thumbnail in your own storage (S3, R2, local disk), and run a background job that refreshes expired thumbnails in batches. Serve from your storage, refresh from the API. The caching strategies guide walks through the full lifecycle, including cache invalidation when a listing is manually updated.
Batch processing thousands of URLs
A directory launch with 2,000 initial listings needs all 2,000 thumbnails generated before the site goes live. Processing them sequentially at 3 seconds per capture takes nearly two hours. Parallelizing with a job queue cuts that to minutes.
const CONCURRENCY = 5;
const API_KEY = process.env.SCREENSHOTRUN_API_KEY;
async function captureThumbnail(url) {
const res = await fetch(
`https://screenshotrun.com/api/v1/screenshots/capture?` +
`url=${encodeURIComponent(url)}&width=1280&height=960` +
`&format=webp&quality=80&resize_width=400` +
`&block_cookies=true&cache_ttl=604800`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);
return res.arrayBuffer();
}
async function processBatch(urls) {
const results = [];
for (let i = 0; i < urls.length; i += CONCURRENCY) {
const batch = urls.slice(i, i + CONCURRENCY);
const screenshots = await Promise.all(
batch.map(url => captureThumbnail(url).catch(err => {
console.error(`Failed: ${url}`, err.message);
return null;
}))
);
results.push(...screenshots);
// Brief pause between batches to stay within rate limits
if (i + CONCURRENCY < urls.length) {
await new Promise(r => setTimeout(r, 1000));
}
}
return results;
}
Five concurrent requests with a one-second pause between batches processes 2,000 URLs in about 15 minutes. The catch on each request ensures one failed URL doesn't block the rest. Failed thumbnails get a placeholder image and go into a retry queue. The rate limiting guide covers the retry logic and backoff strategy for larger batches.
What it costs to thumbnail a directory
Pricing depends on two variables: how many listings and how often you refresh. Here's the math for screenshotrun:
| Directory size | Refresh cycle | Monthly captures | Plan | Monthly cost |
|---|---|---|---|---|
| 100 listings | Weekly | ~400 | Free (200/mo) + cache | $0 |
| 500 listings | Weekly | ~2,000 | Starter ($9/mo) | $9 |
| 2,000 listings | Bi-weekly | ~4,000 | Growth ($29/mo) | $29 |
| 10,000 listings | Weekly | ~40,000 | Scale ($99/mo) | $99 |
A 100-listing directory with smart caching fits on the free tier. A 10,000-listing directory at weekly refresh runs $99 per month. Compare that to maintaining a Puppeteer server: $20-50/month for the VPS plus the engineering time to keep it running. At scale, the API is both cheaper and less maintenance.
For comparison, ScreenshotOne charges $79/month for 10,000 screenshots and $259 for 50,000. Urlbox starts at $99/month for 15,000 Hi-Fi captures. The best screenshot API comparison has the full pricing breakdown across seven providers.
200 free screenshots/month — enough for a small directory
Get your API keyWebsite thumbnails sit at the intersection of simple concept and messy implementation. The API call is one line. But cookie banners, SPA rendering, stale images, and batch processing at scale each add a layer of complexity that grows with the directory. If the link preview angle is closer to your use case (unfurling shared URLs rather than generating directory thumbnails), that page covers the og:image fallback chain and smaller-scale patterns. For teams evaluating whether an API is worth it versus self-hosting, the Puppeteer vs API and Playwright vs API comparisons walk through the infrastructure tradeoffs. And the link directory tutorial has the full Laravel implementation with database schema, Blade templates, and refresh scheduling.
If you're building a directory and need a website thumbnail API that actually shows the website, the free tier covers your first 200 captures. That's enough to validate the approach before committing to a plan.