Code Examples
Copy-paste examples for the v1 endpoint. They read the key from a POSTCAPTURE_API_KEY environment variable — keep it out of your source.
Basic Screenshot
Post the URL, get back a link to the rendered PNG plus your remaining quota.
1curl -X POST https://postcapture.com/api/v1/screenshot \2 -H "Authorization: Bearer $POSTCAPTURE_API_KEY" \3 -H "Content-Type: application/json" \4 -d '{5 "postUrl": "https://x.com/postcaboron/status/1924847327370461555"6 }'
Save the PNG
The hosted URL is valid for at least 1 hour; the image is deleted shortly after. If you need the file, either download it straight away or ask for the bytes directly with "response": "image", which skips the upload entirely.
1# Raw PNG straight to a file2curl -X POST https://postcapture.com/api/v1/screenshot \3 -H "Authorization: Bearer $POSTCAPTURE_API_KEY" \4 -H "Content-Type: application/json" \5 -o screenshot.png \6 -d '{7 "postUrl": "https://x.com/postcaboron/status/1924847327370461555",8 "response": "image"9 }'
Custom Settings
Send only what you want to change. Global options shape the canvas and card; the platform group toggles what the post itself shows. Every field is listed in the v1 reference.
1curl -X POST https://postcapture.com/api/v1/screenshot \2 -H "Authorization: Bearer $POSTCAPTURE_API_KEY" \3 -H "Content-Type: application/json" \4 -d '{5 "postUrl": "https://x.com/postcaboron/status/1924847327370461555",6 "settings": {7 "global": {8 "theme": "dark",9 "padding": 60,10 "scaleFactor": 3,11 "showWatermark": false12 },13 "twitter": {14 "showFollowerCount": false,15 "showViewCount": false16 }17 }18 }'
Multi-Platform
The same endpoint handles every platform — only postUrl changes. Note the settings group differs per platform, so send the one that matches.
1const API_KEY = process.env.POSTCAPTURE_API_KEY;23const posts = [4 { url: "https://x.com/postcaboron/status/1924847327370461555", settings: { twitter: { showViewCount: false } } },5 { url: "https://bsky.app/profile/user.bsky.social/post/abc123", settings: { bluesky: { showDate: false } } },6 { url: "https://youtube.com/watch?v=dQw4w9WgXcQ", settings: { youtube: { youtubeFormat: "portrait" } } },7 { url: "https://tiktok.com/@user/video/1234567890", settings: { tiktok: { showTikTokMusic: false } } },8];910for (const post of posts) {11 const res = await fetch("https://postcapture.com/api/v1/screenshot", {12 method: "POST",13 headers: {14 Authorization: `Bearer ${API_KEY}`,15 "Content-Type": "application/json",16 },17 body: JSON.stringify({ postUrl: post.url, settings: post.settings }),18 });1920 if (!res.ok) {21 const { code } = await res.json();22 console.error(`Failed for ${post.url}: ${res.status} ${code}`);23 continue;24 }2526 const { url, platform } = await res.json();27 console.log(platform, url);28}
Batch Processing
Work through many URLs inside the 10 requests/minute limit, download each render before it expires, and stop early when the monthly quota runs out.
1import { writeFile } from "node:fs/promises";23const API_KEY = process.env.POSTCAPTURE_API_KEY;4const DELAY_MS = 7000; // ~8.5 requests/min, comfortably under the limit56const urls = [7 "https://x.com/postcaboron/status/1924847327370461555",8 "https://x.com/elonmusk/status/1234567890",9 // ... more URLs10];1112async function captureOne(postUrl, index) {13 const res = await fetch("https://postcapture.com/api/v1/screenshot", {14 method: "POST",15 headers: {16 Authorization: `Bearer ${API_KEY}`,17 "Content-Type": "application/json",18 },19 body: JSON.stringify({ postUrl }),20 });2122 if (res.status === 429) {23 const wait = parseInt(res.headers.get("Retry-After") || "10");24 console.log(`Rate limited. Waiting ${wait}s…`);25 await new Promise((r) => setTimeout(r, wait * 1000));26 return captureOne(postUrl, index); // retry27 }2829 if (!res.ok) {30 const { error, code } = await res.json();31 // Out of screenshots for the month: nothing to retry.32 if (code === "quota_exceeded") throw new Error(error);33 console.error(`Skipping ${postUrl}: ${code}`);34 return;35 }3637 const { url, credits } = await res.json();3839 // Download now — the hosted file is removed about an hour after rendering.40 const image = await fetch(url);41 await writeFile(`screenshot-${index}.png`, Buffer.from(await image.arrayBuffer()));42 console.log(`[${index + 1}/${urls.length}] saved · ${credits.remaining} credits left`);43}4445for (let i = 0; i < urls.length; i++) {46 await captureOne(urls[i], i);47 if (i < urls.length - 1) await new Promise((r) => setTimeout(r, DELAY_MS));48}