
Tweet screenshots are everywhere — news articles, newsletters, social proof walls, slide decks. Doing one by hand is easy. Doing five hundred a day for your content pipeline is not, because X aggressively gates automated access: login walls, consent dialogs, and markup that changes without notice.
Here are the approaches that actually hold up, from most to least reliable.
X publishes an oEmbed endpoint that returns official embed HTML for any public tweet — no API key required:
curl "https://publish.twitter.com/oembed?url=https://twitter.com/jack/status/20"
The response contains an html field with a blockquote and the widgets.js script. Render that HTML in a real browser and you get the clean, card-style tweet — the same look the embed has on a webpage. With a screenshot API that accepts raw HTML, the whole pipeline is one request:
const { html } = await (
await fetch(
"https://publish.twitter.com/oembed?url=" +
encodeURIComponent(tweetUrl)
)
).json();
const response = await fetch("https://api.screenshotty.link/api/v1/screenshot", {
method: "POST",
headers: {
"X-Api-Key": process.env.SCREENSHOTTY_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
html: `
selector: ".twitter-tweet",
wait_ms: 2000,
transparent_background: true,
format: "image/png",
}),
});
Why this wins:
For quick jobs, point a capture at the tweet URL itself and crop to the article element:
curl -X POST "https://api.screenshotty.link/api/v1/screenshot" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://x.com/jack/status/20",
"selector": "article",
"block_cookie_banner": true,
"wait_ms": 1500
}'
This shows the tweet as it appears on x.com, including reply context. Caveats: X sometimes interposes login prompts for automated traffic, and the article markup shifts periodically. Treat this as the convenience option and Method 1 as the production option.
Self-hosted Puppeteer against x.com means fighting bot detection, login walls, consent dialogs, and selector churn — on top of [running the browsers themselves](/alternatives/puppeteer). Teams that start here usually end up at Method 1 after the second breakage.
Both methods scale linearly: feed tweet URLs from your CMS or sheet, fire requests concurrently, and use webhook_url for async delivery if you're processing hundreds. Store the PNGs wherever your pipeline already lives.
Try either method with a [free API key](/sign-up) — 100 screenshots/month — or test a capture in the [free screenshot tool](/website-screenshot) first.