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 file
2curl -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": false
12 },
13 "twitter": {
14 "showFollowerCount": false,
15 "showViewCount": false
16 }
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;
2
3const 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];
9
10for (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 });
19
20 if (!res.ok) {
21 const { code } = await res.json();
22 console.error(`Failed for ${post.url}: ${res.status} ${code}`);
23 continue;
24 }
25
26 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";
2
3const API_KEY = process.env.POSTCAPTURE_API_KEY;
4const DELAY_MS = 7000; // ~8.5 requests/min, comfortably under the limit
5
6const urls = [
7 "https://x.com/postcaboron/status/1924847327370461555",
8 "https://x.com/elonmusk/status/1234567890",
9 // ... more URLs
10];
11
12async 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 });
21
22 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); // retry
27 }
28
29 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 }
36
37 const { url, credits } = await res.json();
38
39 // 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}
44
45for (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}