Screenshot Happy
One request, one screenshot. Send a URL, get back a PNG.
Try it
curl "https://screenshot-api-production-ffd7.up.railway.app/screenshot?url=https://example.com" \ -H "x-api-key: YOUR_API_KEY" \ --output screenshot.png
// JavaScript
const res = await fetch("https://screenshot-api-production-ffd7.up.railway.app/screenshot?url=" + encodeURIComponent("https://example.com"), {
headers: { "x-api-key": "YOUR_API_KEY" }
});
const buffer = await res.arrayBuffer();
# Python
import requests
r = requests.get("https://screenshot-api-production-ffd7.up.railway.app/screenshot",
params={"url": "https://example.com"},
headers={"x-api-key": "YOUR_API_KEY"})
open("screenshot.png", "wb").write(r.content)
Parameters
url(required) — the page to capture. Must be http or https, no private/internal hosts.full_page—trueto capture the entire scrollable page instead of just the viewport. Default: viewport only. Capped at 30,000px tall — very long pages return a clear error instead of a slow, oversized image.format—png(default),jpeg, orpdf.pdfrenders as the real page looks (not a print stylesheet), one page sized to the viewport — combine withfull_page=truefor one page sized to the whole content instead. Not compatible withselector.quality— integer 1–100, JPEG compression quality. Only applies withformat=jpeg.width/height— custom viewport size in pixels, 200–2560 each. Default: 1280×800. Use this for mobile-sized captures too, e.g.width=375&height=812.selector— a CSS selector. Captures only that element instead of the whole page/viewport.
15s timeout. Not available yet: WebP, custom headers/cookies, ad-block or cookie-banner removal.
Limits
- 10 requests/minute per API key — applies to every key, regardless of plan.
- Monthly quota depends on your plan (200/2,000/10,000 screenshots) — see Pricing. Demo/legacy keys without a plan get 100/day instead.
- 300 requests/day total across the service.
- 2 screenshots running at once, max.
Over any limit you get 429 with a reintenta_en_segundos field and a Retry-After header — never a silent hang.
Errors
401— missing or invalid API key.400— missing/invalidurl, the host isn't allowed, or a bad value forformat/quality/width/height/selector(see Parameters above).429— rate limit reached, see above.500— the page couldn't be captured (timeout, site blocked headless browsers, etc). Checkdetallein the response.
Monitoring
Same engine as /screenshot, running on a schedule: it captures a URL periodically, compares each capture against the previous one, and calls your webhook when the difference crosses a threshold. Not available on the demo key — needs a real API key (see Pricing for how many pages and how often per plan).
curl -X POST "https://screenshot-api-production-ffd7.up.railway.app/monitors" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"webhook_url": "https://your-server.com/webhook",
"frecuencia_minutos": 60,
"umbral_diferencia": 5
}'
// JavaScript
const res = await fetch("https://screenshot-api-production-ffd7.up.railway.app/monitors", {
method: "POST",
headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({
url: "https://example.com",
webhook_url: "https://your-server.com/webhook",
frecuencia_minutos: 60,
umbral_diferencia: 5
})
});
const monitor = await res.json();
# Python
import requests
r = requests.post("https://screenshot-api-production-ffd7.up.railway.app/monitors",
headers={"x-api-key": "YOUR_API_KEY"},
json={
"url": "https://example.com",
"webhook_url": "https://your-server.com/webhook",
"frecuencia_minutos": 60,
"umbral_diferencia": 5,
})
monitor = r.json()
frecuencia_minutos — minimum check interval in minutes; your plan sets the floor (see Pricing), 15 min is the hard minimum for everyone. umbral_diferencia — % of changed pixels that counts as a real change (default 5).
POST /monitors— create one. Returns{ id, url, webhook_url, frecuencia_minutos, umbral_diferencia, activo, created_at }.GET /monitors— list yours, withultimo_resultado(last comparison) andultima_comprobacion(last check time).DELETE /monitors/:id— remove one.404if it's not yours or doesn't exist.
When a change is detected, we POST this to your webhook_url. Up to 3 attempts if your endpoint doesn't return a 2xx (immediately, then after 5s, then after 30s) — after that we give up on that delivery, no queue or dead-letter yet:
{
"monitor_id": 4,
"url": "https://example.com",
"comprobado_en": "2026-09-18T17:00:00.000Z",
"cambio_detectado": true,
"diferencia_porcentaje": 12.4
}
One monitor check runs at a time across the whole service (simple by design for now) — with very few customers this is invisible, but it means the check frequency your plan promises is a target, not a real-time guarantee, until this scales. Being upfront about it here rather than after you notice.
Pricing
Every plan combines screenshots and change-monitoring in one payment — no separate products, no usage billing to track. Hit your monthly screenshot limit or your plan's page limit for monitors? You get a clear 429 response telling you so — never silent throttling or a surprise charge. Upgrade any time; it takes effect immediately, no waiting for a renewal date. No contracts, cancel whenever (see billing terms).
Free
€0 /month
- 200 screenshots/month
- 3 monitored pages, checked every 6h
Starter
€9 /month
- 2,000 screenshots/month
- 20 monitored pages, checked every 1h, webhook alerts on change
Growth
€29 /month
- 10,000 screenshots/month
- 100 monitored pages, checked every 15–30 min, webhook alerts on change
Need more than Growth? Contact us directly.
Already subscribed?
Manage your subscription, update your card, see invoices, or cancel — enter your API key.
Something broken?
Tell us what happened. We read every message.