v1 · Beta

Screenshot API

One POST request turns a public post URL into a rendered PNG. The platform is detected from the URL, and every rendering option is optional — send just the URL and you get the defaults.

The API is at v1 and currently in beta — it is ready to build on, but details may still change while we gather feedback.

Endpoint

POST
/api/v1/screenshot

Send a JSON body with Content-Type: application/json. POST is the only method this endpoint accepts.

Authentication

Pass your API key as a Bearer token. Keys are created in Dashboard → API once API access is enabled for your account — see Authentication.

1Authorization: Bearer sk_your_api_key

Request Body

ParameterTypeDefaultDescription
postUrl*stringURL of the post to capture. The platform is detected from the domain; anything outside X/Twitter, Bluesky, YouTube and TikTok is rejected.
responseenum"json"What comes back. "json" uploads the render and returns its URL and metadata; "image" streams the PNG bytes in the response body. One of "json", "image".
settingsobject{}Rendering options, grouped by global / twitter / bluesky / youtube / tiktok. Every field is optional and falls back to its default. Unknown fields are rejected with a 400.
1{
2 "postUrl": "https://x.com/postcaboron/status/1924847327370461555",
3 "settings": {
4 "global": {
5 "theme": "dark",
6 "platformConfig": "format:portrait",
7 "padding": 60,
8 "cardStyle": "glass",
9 "showWatermark": false
10 },
11 "twitter": {
12 "showFollowerCount": false,
13 "showViewCount": false
14 }
15 }
16}

Platform Detection

The platform comes from the postUrl domain and decides which settings group applies. A URL that matches none of these is rejected with unsupported_platform.

DomainplatformSettings group
x.com, twitter.comtwittersettings.twitter
bsky.appbskysettings.bluesky
youtube.com, youtu.beyoutubesettings.youtube
tiktok.comtiktoksettings.tiktok

Settings

Every field below is optional; anything you omit uses the default in the table. Each group is strict, so a misspelled field is a 400 instead of being silently ignored. Send only the group that matches the post you are capturing: the platform groups share field names such as showLikeCount, and sending several at once lets them collide.

settings.global

Canvas, background and typography — these apply to every platform.

ParameterTypeDefaultDescription
settings.global.platformConfigstringCanvas preset written as "platform:layout", e.g. "twitter:single-image" or "format:portrait". Sets selectedPlatform, selectedLayout, width and height in one go, overriding any of those you send alongside it.
settings.global.themestring"light"Colour scheme of the post card itself.
settings.global.backgroundColorstring"#B4D455"Solid colour behind the card, as a hex string. Send "" for no colour. A gradient in backgroundImage paints over it.
settings.global.backgroundImagestring"linear-gradient(to top left,…CSS gradient painted behind the card, e.g. "linear-gradient(to top left, #0EA5E9, #1E3A8A)". Send "" to fall back to the solid backgroundColor.
settings.global.backgroundSizestring""CSS background-size for the gradient layer, e.g. "200% 200%". Empty leaves the browser default.
settings.global.backgroundLabelstring"Wide Matrix"Name of the background preset. Cosmetic: it is stored with the render for your dashboard history and does not change the image.
settings.global.widthnumber1200Canvas width in pixels. A selectedPlatform/selectedLayout preset (or platformConfig) overrides it; in auto mode it caps how wide the render may grow. Range 100-5000. Whole numbers only.
settings.global.paddingnumber35Space between the card and the edge of the canvas, in pixels. Applies in auto mode — fixed canvases use the preset's safe area and selectedPostSize instead. Range 0-500.
settings.global.textSizeenum"xl"Size of the post text. One of "sm", "lg", "xl", "2xl", "3xl", "4xl", "5xl", "6xl".
settings.global.scaleFactornumber2Pixel-density multiplier for the render. 2 gives a retina-quality PNG at twice the canvas dimensions. Range 1-4.
settings.global.backgroundImageUrlstring""Public URL of a background image. Replaces backgroundColor and backgroundImage when set. Pro.
settings.global.imageFitenum"cover"How the background image fills the canvas. One of "cover", "contain", "fill".
settings.global.selectedImageIdstring""ID of a PostCapture preset background image, resolved to its URL. Ignored when you send backgroundImageUrl yourself.
settings.global.customSolidColorstring"#3B82F6"Solid colour stored by the custom-background picker, as a hex string.
settings.global.customGradientColor1string"#3B82F6"First colour stop of the custom gradient, as a hex string.
settings.global.customGradientColor2string"#8B5CF6"Second colour stop of the custom gradient, as a hex string.
settings.global.customGradientDirectionstring"to right"Direction of the custom gradient, e.g. "to right" or "135deg".
settings.global.showWatermarkbooleantrueShow the PostCapture watermark. Forced on for free accounts — see the note under Plan limits.
settings.global.showShadowbooleantrueDrop shadow behind the card.
settings.global.cardStyleenum"classic"Chrome drawn around the post. "frame", "sticker", "polaroid" and "tape" are Pro styles. One of "classic", "glass", "window", "browser", "frame", "sticker", "polaroid", "tape", "minimal".
settings.global.postLayoutenum"standard"How the post content is arranged inside the card. "poster", "bubble", "marker", "receipt" and "magazine" are Pro layouts. One of "standard", "poster", "bubble", "marker", "quote", "statement", "chat", "split", "receipt", "magazine".
settings.global.fontFamilystring""Font applied to the whole card. Empty keeps the platform font. Pro.
settings.global.textColorstring""Colour of the post text, as a hex string. Empty keeps the theme colour. Pro.
settings.global.linkColorstring""Colour of links and @-mentions, as a hex string. Empty keeps the theme colour. Pro.
settings.global.quoteTextColorstring""Colour of quoted-post text, as a hex string. Empty keeps the theme colour. Pro.
settings.global.selectedPlatformenum"twitter"Which family of canvas presets to size the image with. "format" gives plain aspect ratios (square, portrait, landscape) and "auto" sizes to the content. One of "auto", "format", "twitter", "facebook", "instagram", "linkedin", "youtube", "pinterest", "reddit", "snapchat", "tiktok".
settings.global.selectedLayoutstring"single-image"Preset within selectedPlatform, e.g. "single-image", "portrait" or "video-thumbnail". "auto" turns on auto mode.
settings.global.selectedPostSizeenum"large"How large the card is drawn inside a fixed canvas. Ignored in auto mode. One of "small", "medium", "large".
settings.global.heightnumber675Canvas height in pixels. A selectedPlatform/selectedLayout preset (or platformConfig) overrides it; in auto mode it caps how tall the render may grow. Range 100-5000. Whole numbers only.
settings.global.isAutoModebooleanfalseSize the canvas to the content instead of a fixed preset, using width and height as the upper bound.

settings.twitter

ParameterTypeDefaultDescription
settings.twitter.showThreadbooleanfalseRender the whole thread instead of the single post.
settings.twitter.showReplybooleantrueShow the reply icon and count in the action row.
settings.twitter.showViewCountbooleantrueShow the view count.
settings.twitter.showFollowerCountbooleantrueShow the author's follower count next to their handle.
settings.twitter.showLikeCountbooleantrueShow the like count.
settings.twitter.showRepostCountbooleantrueShow the repost count.
settings.twitter.showBookmarkCountbooleantrueShow the bookmark count.
settings.twitter.showContentLabelbooleantrueShow sensitive-content and similar labels X attaches to the post.
settings.twitter.showCommunitybooleantrueShow the community a post was published in.
settings.twitter.showCommunityRolebooleanfalseShow the author's role in that community, e.g. moderator.
settings.twitter.showReplyTobooleantrueShow the "Replying to @handle" line above the post.
settings.twitter.showFullQuotebooleantrueRender the quoted post in full rather than as a compact card.
settings.twitter.showDatebooleantrueShow the post's timestamp.
settings.twitter.showMediabooleantrueInclude the post's images and video thumbnails.
settings.twitter.showLinkCardsbooleantrueRender the link preview card for URLs in the post.
settings.twitter.showQuoteMediabooleantrueInclude media attached to the quoted post.

settings.bluesky

ParameterTypeDefaultDescription
settings.bluesky.showReplybooleantrueShow the reply icon and count in the action row.
settings.bluesky.showLikeCountbooleantrueShow the like count.
settings.bluesky.showRepostCountbooleantrueShow the repost count.
settings.bluesky.showDatebooleantrueShow the post's timestamp.
settings.bluesky.showFollowerCountbooleantrueShow the author's follower count next to their handle.
settings.bluesky.showMediabooleantrueInclude the post's images.
settings.bluesky.showLinkCardsbooleantrueRender the link preview card for URLs in the post.
settings.bluesky.showQuoteMediabooleantrueInclude media attached to the quoted post.

settings.youtube

ParameterTypeDefaultDescription
settings.youtube.showPostedSincebooleantrueShow the relative upload time, e.g. "3 days ago".
settings.youtube.youtubeFormatenum"landscape"Orientation of the video card. One of "landscape", "portrait".
settings.youtube.showFollowerCountbooleantrueShow the channel's subscriber count.
settings.youtube.showLikeCountbooleantrueShow the like count.
settings.youtube.showViewCountbooleantrueShow the view count.
settings.youtube.youtubeThumbnailnumber0Which still represents the video: 0 is the uploader's thumbnail, 1-3 are the frames YouTube grabs automatically. Range 0-3. Whole numbers only.

settings.tiktok

ParameterTypeDefaultDescription
settings.tiktok.tiktokFormatenum"landscape"Orientation of the video card. One of "landscape", "portrait".
settings.tiktok.showTikTokMusicbooleantrueShow the sound the video uses.
settings.tiktok.showTikTokDescriptionbooleantrueShow the video caption.
settings.tiktok.showFollowerCountbooleantrueShow the creator's follower count.
settings.tiktok.showLikeCountbooleantrueShow the like count.
settings.tiktok.showViewCountbooleantrueShow the view count.
settings.tiktok.showRepostCountbooleantrueShow the repost count.
settings.tiktok.showBookmarkCountbooleantrueShow the bookmark count.
settings.tiktok.tiktokThumbnailnumber0Which cover represents the video: 0 is the feed cover, higher values pick the untouched frame and any share covers. How many exist varies per video, and an out-of-range value falls back to the feed cover. Range 0-10. Whole numbers only.

Response

JSON (default)

The render is uploaded and you get back its URL plus metadata and your remaining quota.

1{
2 "url": "https://screenshots-cdn.postcapture.com/api-renders/hK3q…/1924847327370461555-2026-07-25-11-04-12-a9f2.png",
3 "expiresAt": "2026-07-25T12:04:12.918Z",
4 "width": 2400,
5 "height": 1350,
6 "contentType": "image/png",
7 "platform": "twitter",
8 "historyId": "cm9f3k2p10001v8m2v0k4a1bd",
9 "credits": {
10 "remaining": 2989,
11 "limit": 3000,
12 "used": 11,
13 "resetsAt": "2026-08-01T00:00:00.000Z",
14 "platforms": {
15 "twitter": { "used": 11, "limit": 1000, "remaining": 989 },
16 "bsky": { "used": 0, "limit": 1000, "remaining": 1000 },
17 "youtube": { "used": 0, "limit": 1000, "remaining": 1000 },
18 "tiktok": { "used": 0, "limit": 1000, "remaining": 1000 }
19 }
20 }
21}
FieldTypeDescription
urlstringPublic link to the rendered PNG.
expiresAtstringISO timestamp one hour after the render, when the file becomes eligible for deletion.
widthnumber | nullPixel width of the PNG (canvas width × scaleFactor). Null if the dimensions could not be read.
heightnumber | nullPixel height of the PNG (canvas height × scaleFactor). Null if the dimensions could not be read.
contentTypestringAlways "image/png".
platformstringPlatform detected from postUrl: "twitter", "bsky", "youtube" or "tiktok".
historyIdstring | nullID of the entry created in your dashboard history. Null if the entry could not be written; the render still succeeded.
credits.remainingnumberScreenshots left this month after this call.
credits.limitnumberScreenshots included in your plan each month.
credits.usednumberScreenshots used this month, including this call.
credits.resetsAtstringISO timestamp when the monthly quota resets.
credits.platformsobject | undefinedPer-platform usage this month, keyed by platform ("twitter", "bsky", "youtube", "tiktok"), each with used, limit and remaining. Present only on plans that cap how much of the monthly quota a single platform may use — the API plan allows 1000 per platform out of its 3000. Omitted entirely on plans with no such cap.

Image retention

The URL is valid for at least 1 hour; the image is deleted shortly after. Download the file and store it yourself if you need it for longer — expiresAt tells you when it becomes eligible for deletion. Copies of your renders stay in your dashboard history for as long as your plan keeps them.

Raw image

Send "response": "image" to get the PNG bytes in the response body instead. Nothing is uploaded, so there is no URL and no expiry to worry about — but you also get no quota metadata back.

Content-Typeimage/png
Cache-Controlpublic, max-age=60

Errors

Every error is JSON with a human-readable error and a stable code to branch on.

StatuscodeWhen
400invalid_jsonThe request body was not valid JSON.
400invalid_requestThe body failed validation — a missing postUrl, a value out of range, or an unknown settings field. The issues array names each offending path.
400unsupported_platformpostUrl is a valid URL but not an X/Twitter, Bluesky, YouTube or TikTok post.
401missing_api_keyNo Authorization: Bearer header was sent.
401invalid_api_keyThe key is unknown, disabled or expired. details carries the reason.
402quota_exceededYour monthly screenshots are used up. The body carries exceeded: true plus limit and used.
402platform_quota_exceededThis platform has used its share of the monthly quota, though screenshots remain overall. The body carries the platform, its limit and used, plus a credits block whose platforms map shows where you still have room.
403api_access_disabledAPI access is not active for your account. It is included with the API plan.
429rate_limitedMore than 10 requests in a minute. Retry after the Retry-After header (seconds) or retryAfterMs (milliseconds).
500unknown_userThe key verified but no account could be resolved for it. Contact support if you see this.
500render_failedRendering the post failed, e.g. the post is private, deleted, or the renderer timed out. No credit is used.
500storage_failedThe render succeeded but could not be stored. No credit is used — retry the request.

Validation failures name every problem at once, so you can fix a payload in one pass:

1{
2 "error": "Invalid request body",
3 "code": "invalid_request",
4 "issues": [
5 { "path": "settings.global.widht", "message": "Unrecognized key(s) in object: 'widht'" },
6 { "path": "settings.global.scaleFactor", "message": "Number must be less than or equal to 4" }
7 ]
8}

Credits & Plan Limits

  • One credit per successful call. Nothing is charged for rejected requests, and nothing is charged when rendering or storing the image fails. The quota resets on the 1st of each month.
  • The watermark is forced on for free accounts. Sending settings.global.showWatermark: false is accepted but has no effect until you upgrade — the server sets it back on before rendering.
  • Pro unlocks the premium looks. Premium card styles and post layouts, custom image backgrounds, and custom fonts and colours are part of the Pro plan.
  • 10 requests per minute. Shared across all of your API keys, and across v1 and the legacy endpoint — see Rate Limits.

Examples

The smallest request that works — a post URL and your key:

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 }'

The same call with a portrait canvas, a dark glass card and a couple of counters hidden:

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 "platformConfig": "format:portrait",
10 "padding": 60,
11 "cardStyle": "glass",
12 "showWatermark": false
13 },
14 "twitter": {
15 "showFollowerCount": false,
16 "showViewCount": false
17 }
18 }
19 }'

More patterns — batching, retries and every platform — are in Code Examples.

Moving from the legacy endpoint

The legacy GET /api/screenshot endpoint keeps working, but it is no longer documented. What changes when you move:

  • GET /api/screenshot with query params becomes POST /api/v1/screenshot with a JSON body — no more URL-length ceiling on your settings.
  • The apiKey query param becomes an Authorization: Bearer header, so keys stop showing up in URLs and logs.
  • Flat settings become grouped: &showLikeCount=false becomes settings.twitter.showLikeCount: false.
  • Typos are errors now. The legacy endpoint forwarded unknown params and quietly ignored them; v1 rejects them with invalid_request.
  • You get JSON by default instead of PNG bytes. Keep the old behaviour with "response": "image".