Shots API Documentation

Shots exposes editor utilities for capture, export compression, tweet resolution, and approved media proxying. Each one requires a signed-in session or a workspace API key in X-API-Key. It also exposes authenticated /api/v1 tenant operations for workspace automation. Every failing request uses the shared JSON error envelope. The machine-readable contract lives at /openapi.json.

Quickstart

Create a workspace API key in Settings → Developer API (/workspace/developers), then capture a page and write the PNG to disk in one command.

curl -s -X POST https://shots.playbookmedia.io/api/screenshot \
  -H "X-API-Key: $SHOTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}' \
  | jq -r .screenshot | base64 -d > shot.png

Base URL

https://shots.playbookmedia.io

Endpoints

POST/api/screenshot

operationId: captureScreenshot

Renders the page at the given URL and returns the screenshot as a base64-encoded PNG. Results are cached per URL, device type, and color scheme.

Request

curl -X POST https://shots.playbookmedia.io/api/screenshot \
  -H "X-API-Key: $SHOTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "deviceType": "desktop",
    "colorScheme": "light",
    "forceRefresh": false
  }'

Response

{
  "screenshot": "iVBORw0KGgoAAAANSUhEUg...",
  "url": "https://example.com",
  "cached": false,
  "strategy": "microlink",
  "deviceType": "desktop",
  "colorScheme": "light"
}

POST/api/export

operationId: optimizeExportImage

Recompresses an image with Sharp and returns the optimized bytes. JPEG uses MozJPEG, WebP uses libwebp, PNG uses adaptive filtering. The response body is the image itself, not JSON.

Request

curl -X POST https://shots.playbookmedia.io/api/export \
  -H "X-API-Key: $SHOTS_API_KEY" \
  -F "[email protected]" \
  -F "format=webp" \
  -F "qualityPreset=high" \
  -o shot.webp

Response

HTTP/2 200
content-type: image/webp

<binary image bytes>

GET/api/tweet/{id}

operationId: getTweet

Returns the public tweet payload used to render a tweet as an image. The id is the numeric status ID from the tweet URL.

Request

curl https://shots.playbookmedia.io/api/tweet/1234567890123456789 \
  -H "X-API-Key: $SHOTS_API_KEY"

Response

{
  "data": {
    "id_str": "1234567890123456789",
    "text": "...",
    "user": { "name": "...", "screen_name": "..." }
  }
}

GET/api/image-proxy

operationId: proxyTwitterImage

Streams a Twitter-hosted image through this origin so it can be drawn onto a canvas without tainting it. Only pbs.twimg.com, abs.twimg.com, ton.twitter.com, and video.twimg.com are allowed.

Request

curl "https://shots.playbookmedia.io/api/image-proxy?url=https://pbs.twimg.com/media/EXAMPLE.jpg" \
  -H "X-API-Key: $SHOTS_API_KEY" \
  -o media.jpg

Response

HTTP/2 200
content-type: image/jpeg

<binary image bytes>

Errors

Every failing request returns JSON with the same shape. Branch on code, which is stable; error and message carry the same human-readable text, and hint explains how to recover.

{
  "error": "URL is required",
  "code": "invalid_request",
  "message": "URL is required",
  "hint": "Send a JSON body with a \"url\" string, for example {\"url\": \"https://example.com\"}.",
  "status": 400,
  "documentation": "https://shots.playbookmedia.io/docs#errors"
}
CodeStatusMeaning
invalid_request400A required field is missing or malformed.
invalid_url400The url is not a valid absolute http or https URL.
unsupported_value400A field was set to a value outside its allowed enum.
unauthorized401No signed-in session or workspace API key was sent.
forbidden_domain403The requested host is not on the proxy allowlist.
not_found404No endpoint or resource matches the request.
method_not_allowed405The endpoint does not accept this HTTP method.
rate_limited429The per-IP rate limit was exceeded. Honour Retry-After.
upstream_timeout408The target page took too long to load.
upstream_unavailable503The upstream capture service is unreachable.
upstream_failed502The upstream host refused or failed the request.
internal_error500Unexpected server-side failure.

Markdown content negotiation

Every page on this site serves Markdown to clients that ask for it. Responses set Content-Type: text/markdown; charset=utf-8 and Vary: Accept, Accept-Encoding. A request that accepts neither text/html nor text/markdown is answered with 406, and an unknown path returns 404 with a Markdown body listing where to look next.

curl -H "Accept: text/markdown" https://shots.playbookmedia.io/

Related resources