You need a thumbnail of a user-submitted URL. Maybe it's a link preview in a dashboard, a visual record attached to a saved bookmark, or a proof-of-state image in an audit log. The usual answer is to install Playwright, ship Chromium into your container, and then own a headless browser in production forever.
SiteIntel does that part for you. One GET, PNG bytes back.
The request
/v1/screenshot takes a full https:// URL (not a bare domain) and responds with image/png:
curl -s -X GET \
'https://siteintel.p.rapidapi.com/v1/screenshot?url=https://stripe.com' \
-H 'X-RapidAPI-Key: YOUR_KEY' \
-H 'X-RapidAPI-Host: siteintel.p.rapidapi.com' \
--output stripe.png
That's the whole integration. The response body is the image, so pipe it to a file, an S3 upload, or straight through to your own res.
In Node, using global fetch (18+, no dependencies):
const API = 'https://siteintel.p.rapidapi.com';
const headers = {
'X-RapidAPI-Key': process.env.RAPIDAPI_KEY,
'X-RapidAPI-Host': 'siteintel.p.rapidapi.com',
};
async function screenshot(url) {
const res = await fetch(`${API}/v1/screenshot?url=${encodeURIComponent(url)}`, { headers });
if (!res.ok) throw new Error(`screenshot failed: ${res.status}`);
return Buffer.from(await res.arrayBuffer()); // PNG bytes
}
const png = await screenshot('https://stripe.com');
await fs.promises.writeFile('stripe.png', png);
Two things worth knowing. encodeURIComponent matters here: the target URL has its own ? and &, and skipping the encode is the most common way this call silently grabs the wrong page. And on failure the API returns JSON instead of PNG, so check res.ok before you treat the body as an image.
The metadata that goes with it
A screenshot alone is a picture with no text. /v1/analyze fills in what the page actually is:
async function analyze(url) {
const res = await fetch(`${API}/v1/analyze?url=${encodeURIComponent(url)}`, { headers });
if (!res.ok) throw new Error(`analyze failed: ${res.status}`);
return res.json();
}
const site = await analyze('https://stripe.com');
console.log(site.title, site.detected_tech);
The shape:
{
"query": "https://stripe.com",
"final_url": "https://stripe.com/",
"status": 200,
"fetched_at": "2026-07-31T14:02:11Z",
"title": "Stripe | Financial Infrastructure to Grow Your Revenue",
"description": "Stripe powers online and in-person payment processing...",
"canonical": "https://stripe.com/",
"lang": "en",
"favicon": "https://stripe.com/favicon.ico",
"open_graph": {
"title": "Stripe | Financial Infrastructure",
"image": "https://images.stripecdn.com/...",
"site_name": "Stripe",
"type": "website"
},
"detected_tech": ["Cloudflare", "React"],
"social_links": ["https://twitter.com/stripe"],
"emails": [],
"server": "nginx"
}
detected_tech and social_links are flat arrays of strings, so no unwrapping objects to get at a name. final_url is what you actually landed on after redirects, which is the field you want to store as the canonical record rather than whatever the user typed. status is the target site's status code, not the API's; a 200 from SiteIntel carrying "status": 404 means the request worked and the page is gone.
One thing to build with it
A link-preview card that doesn't depend on the site having good Open Graph tags. Call /v1/analyze first, use open_graph.image when it's there, and fall back to /v1/screenshot when it isn't:
async function preview(url) {
const site = await analyze(url);
return {
url: site.final_url,
title: site.title,
description: site.description,
favicon: site.favicon,
image: site.open_graph?.image || null, // else render the screenshot endpoint
};
}
If image comes back null, point your <img> at your own proxy route that pipes the screenshot bytes through. Cache the PNG keyed on final_url and store fetched_at alongside it so you know when to refresh. Most pages don't change often enough to justify a fresh capture on every render.
For the SEO side of the same site, /v1/seo-audit?url=https://example.com returns an on-page report, which pairs well if you're building anything that grades or monitors URLs rather than just displaying them.
Working examples live in the repo: github.com/clause-netizen/siteintel-api — it's also on RapidAPI if you want a managed key.
Top comments (0)