API documentation

Base URL https://api.shotium.com/v1 · errors follow RFC 9457 · all endpoints return image bytes on success.

Quick start

  1. Sign in with GitHub — your account starts with 100 free render credits.
  2. Create an API key on the account page. The key (and your signing secret) are shown once.
  3. Take your first screenshot:
curl -H "Authorization: Bearer sk_live_…" \
  "https://api.shotium.com/v1/screenshot?url=https://example.com" \
  -o shot.png

Authentication

Send your API key as a Bearer token: Authorization: Bearer sk_live_…. Keys can be revoked instantly from your account page. The signed-URL form of GET /v1/og-image uses an HMAC signature instead (see below) — no key in the URL, safe to embed publicly.

GET /v1/screenshot

Renders any public URL to an image. Responses are cached for 24 hours — identical parameter sets return the cached artifact (and are billed as a render).

ParamTypeDefaultNotes
urlstringrequiredhttp(s) URL, ≤2048 chars. Fragments are ignored.
widthint1280Viewport width, 1–3840
heightint800Viewport height, 1–2160
full_pageboolfalseCapture full scroll height (≤20,000px)
formatenumpngpng | jpeg | webp
qualityint801–100; lossy formats only, ignored for png
# full-page WebP at 90 quality
curl -H "Authorization: Bearer sk_live_…" \
  "https://api.shotium.com/v1/screenshot?url=https://news.ycombinator.com&full_page=true&format=webp&quality=90" \
  -o page.webp
// Node.js
const res = await fetch(
  'https://api.shotium.com/v1/screenshot?url=https://example.com&format=jpeg',
  { headers: { Authorization: `Bearer ${process.env.SHOTIUM_KEY}` } },
)
const image = Buffer.from(await res.arrayBuffer())

POST /v1/og-image

Renders a 1200×630 Open Graph image from one of five templates (browse templates & parameters). Best for build-time generation.

curl -X POST https://api.shotium.com/v1/og-image \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "blog",
    "params": { "title": "Shipping fast without breaking things", "author": "Ada L." },
    "format": "png"
  }' -o og.png

Body: template (id), params (template-specific, see /og-templates), optional format (png default, or jpeg).

GET /v1/og-image — signed URLs

Embed OG images directly in your HTML <head>. The URL carries an HMAC signature instead of your API key, and responses are served with Cache-Control: public, max-age=86400, immutable so Cloudflare's edge absorbs crawler traffic.

Query = template + template params + uid (your user id) + sig. Sign in your backend:

  1. Collect all query params except sig — include uid, template, and every template param.
  2. Sort keys by byte order; RFC 3986-encode each key and value; join as k=v&k=v.
  3. sig = hex(HMAC-SHA256(canonical_string, signing_secret)) — append as the sig param.
// Node.js
import { createHmac } from 'node:crypto'

const params = {
  template: 'minimal',
  title: 'Less, but better.',
  uid: 'YOUR-UID',
}
const canonical = Object.entries(params)
  .sort(([a], [b]) => (a < b ? -1 : 1))
  .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
  .join('&')
const sig = createHmac('sha256', process.env.SHOTIUM_SIGNING_SECRET)
  .update(canonical).digest('hex')

const url = `https://api.shotium.com/v1/og-image?${canonical}&sig=${sig}`
# Python
import hashlib, hmac, urllib.parse

params = {"template": "minimal", "title": "Less, but better.", "uid": "YOUR-UID"}
canonical = "&".join(
    f"{urllib.parse.quote(k, safe='')}={urllib.parse.quote(v, safe='')}"
    for k, v in sorted(params.items())
)
sig = hmac.new(SIGNING_SECRET.encode(), canonical.encode(), hashlib.sha256).hexdigest()
url = f"https://api.shotium.com/v1/og-image?{canonical}&sig={sig}"

Your uid and signing_secret are issued together with your API key. A signature is valid only for that exact parameter set — changing any value invalidates it.

Rate limits, caching & billing

  • Rate limit: 60 requests/minute per API key (signed URLs: per user). Exceeding returns 429 rate_limited with Retry-After.
  • Caching: identical parameter sets are cached for 24 hours and served without re-rendering.
  • Billing: a render is billed when an image is successfully returned — including cache hits. Failed renders are never billed.
  • Quota: subscription quota resets each billing period; after quota, credits are consumed. When both run out you get 429 quota_exceeded — nothing auto-charges.

Errors

All errors are application/problem+json (RFC 9457) with a stable type you can branch on:

typeHTTPWhen
unauthorized401Missing/invalid API key, or signed-URL signature failed
invalid_params400Request parameters failed validation
invalid_url400Target is not a valid http(s) URL
blocked_target403Target resolves to a blocked network range (SSRF guard)
render_timeout504Render did not complete within 30s
render_failed502Navigation/render error (DNS failure, connection refused…)
storage_error502Artifact storage unavailable — not billed
quota_exceeded429Monthly quota and credits exhausted
rate_limited429Over 60 requests/minute — includes Retry-After header
internal_error500Unexpected server error
{
  "type": "quota_exceeded",
  "title": "Monthly quota exceeded",
  "status": 429,
  "detail": "monthly quota and credits exhausted; upgrade or buy credits at https://shotium.com/pricing"
}

Need help?

Email [email protected] — see /contact for response times.