API documentation
Base URL https://api.shotium.com/v1 · errors follow RFC 9457 · all endpoints return image bytes on success.
Quick start
- Sign in with GitHub — your account starts with 100 free render credits.
- Create an API key on the account page. The key (and your signing secret) are shown once.
- Take your first screenshot:
curl -H "Authorization: Bearer sk_live_…" \
"https://api.shotium.com/v1/screenshot?url=https://example.com" \
-o shot.pngAuthentication
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).
| Param | Type | Default | Notes |
|---|---|---|---|
url | string | required | http(s) URL, ≤2048 chars. Fragments are ignored. |
width | int | 1280 | Viewport width, 1–3840 |
height | int | 800 | Viewport height, 1–2160 |
full_page | bool | false | Capture full scroll height (≤20,000px) |
format | enum | png | png | jpeg | webp |
quality | int | 80 | 1–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.pngBody: 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:
- Collect all query params except
sig— includeuid,template, and every template param. - Sort keys by byte order; RFC 3986-encode each key and value; join as
k=v&k=v. sig = hex(HMAC-SHA256(canonical_string, signing_secret))— append as thesigparam.
// 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_limitedwithRetry-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:
| type | HTTP | When |
|---|---|---|
unauthorized | 401 | Missing/invalid API key, or signed-URL signature failed |
invalid_params | 400 | Request parameters failed validation |
invalid_url | 400 | Target is not a valid http(s) URL |
blocked_target | 403 | Target resolves to a blocked network range (SSRF guard) |
render_timeout | 504 | Render did not complete within 30s |
render_failed | 502 | Navigation/render error (DNS failure, connection refused…) |
storage_error | 502 | Artifact storage unavailable — not billed |
quota_exceeded | 429 | Monthly quota and credits exhausted |
rate_limited | 429 | Over 60 requests/minute — includes Retry-After header |
internal_error | 500 | Unexpected 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.