Screenshot API for Python.
Shotium is a hosted screenshot API you call from Python with a single GET request: no Selenium, no Playwright, no Chromium binaries in your deployment. Send a URL, get back PNG, JPEG or WebP bytes — with full-page capture, template-based OG images, and HMAC-signed URLs you can embed in public HTML.
How do I take a screenshot in Python?
One GET request with the requests library. Sign in, create an API key, and this runs as-is:
import os
import requests
resp = requests.get(
"https://api.shotium.com/v1/screenshot",
params={"url": "https://example.com", "format": "png"},
headers={"Authorization": f"Bearer {os.environ['SHOTIUM_KEY']}"},
timeout=60,
)
resp.raise_for_status()
with open("shot.png", "wb") as f:
f.write(resp.content)Full-page screenshots and viewport control
Set full_page to capture the entire scroll height (up to 20,000px). Requests' params dict takes the string "true" — Python's True would serialize capitalized:
resp = requests.get(
"https://api.shotium.com/v1/screenshot",
params={
"url": "https://news.ycombinator.com",
"full_page": "true",
"format": "webp",
"quality": 90,
"width": 1280,
},
headers={"Authorization": f"Bearer {os.environ['SHOTIUM_KEY']}"},
timeout=60,
)Generate OG images from templates
POST typed parameters into one of five built-in templates and get a finished 1200×630 social card back — no HTML to write:
resp = requests.post(
"https://api.shotium.com/v1/og-image",
json={
"template": "blog",
"params": {"title": "Shipping fast without breaking things", "author": "Ada L."},
"format": "png",
},
headers={"Authorization": f"Bearer {os.environ['SHOTIUM_KEY']}"},
timeout=60,
)
resp.raise_for_status()
with open("og.png", "wb") as f:
f.write(resp.content)How do I embed OG images without exposing my API key?
Sign the query string with your signing secret instead of sending your key. The signed URL is safe to print into public HTML — parameters are tamper-proof, and repeat fetches are served from the CDN edge for free:
import hashlib, hmac, os, 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(
os.environ["SHOTIUM_SIGNING_SECRET"].encode(), canonical.encode(), hashlib.sha256
).hexdigest()
url = f"https://api.shotium.com/v1/og-image?{canonical}&sig={sig}"Error handling
Every error is RFC 9457 problem+json with a stable type field you can branch on. Failed renders are never billed:
resp = requests.get(
"https://api.shotium.com/v1/screenshot",
params={"url": target},
headers={"Authorization": f"Bearer {os.environ['SHOTIUM_KEY']}"},
timeout=60,
)
if resp.status_code != 200:
problem = resp.json() # {"type": "...", "title": "...", "status": ...}
if problem["type"] == "rate_limited":
wait = int(resp.headers.get("Retry-After", "1"))
elif problem["type"] == "render_timeout":
... # site took >30s — retry or skipParameters at a glance
| Param | Default | Notes |
|---|---|---|
url | required | http(s) URL to render |
width / height | 1280 × 800 | Viewport, up to 3840 × 2160 |
full_page | false | Full scroll height, ≤20,000px |
format | png | png | jpeg | webp |
quality | 80 | 1–100, lossy formats only |
Full reference — auth, OG templates, rate limits, error table — in the docs. Template parameters live on /og-templates.
Frequently asked questions
Do I need Selenium or Playwright to take screenshots in Python?
No. Shotium runs the browsers server-side — from Python it's one GET request with the requests library (or httpx, or aiohttp). No webdriver, no browser binaries, no version drift between Chromium and your driver.
How do I take a full-page screenshot in Python?
Add full_page=true to the request. The renderer scrolls the page, waits for lazy-loaded content to settle, and captures up to 20,000px of height. Combine with format=webp and a quality setting to keep long captures small.
Does the Shotium API work with async Python?
Yes — it's plain HTTPS, so httpx.AsyncClient and aiohttp work unchanged. The rate limit is 60 requests/minute per API key; a 429 response includes a Retry-After header your client can honor.
How should Python code react to a failed render?
Check the status code before touching resp.content — a non-200 body is RFC 9457 problem+json, not image bytes, so writing it straight to a .png produces a corrupt file. Branch on problem["type"]: rate_limited means sleep for Retry-After seconds, render_timeout means the target took over 30 seconds. Failed renders are never billed.
Try it on your own pages
Sign-up is GitHub OAuth and comes with 100 free render credits — no card. Failed renders never bill. Plans from $15/month on pricing.