Anonymous API (No Signup Required)
The fastest way to capture a screenshot. No authentication requiredβjust pass a public URL and get back an image.
Simple GET Request
curl "https://snap.i2dev.com/v1/screenshot?url=https%3A%2F%2Fexample.com"Automated clients can save the image while checking the real HTTP status and headers:
curl --silent --show-error \
--output screenshot.jpg \
--dump-header screenshot.headers \
--write-out 'HTTP %{http_code}\n' \
"https://snap.i2dev.com/v1/screenshot?url=https%3A%2F%2Fexample.com"Returns an image directly. Perfect for embeds, <img /> tags, and quick testing. If a capture fails, this endpoint still returns an uncached placeholder image describing the problem.
<!-- Display the public image -->
<img src="https://snap.i2dev.com/v1/screenshot?url=https%3A%2F%2Fexample.com" />
<!-- CORS mode: JavaScript/canvas can read the pixels -->
<img crossorigin="anonymous" src="https://snap.i2dev.com/v1/screenshot?url=https%3A%2F%2Fexample.com" />crossorigin="anonymous" enables CORS without sending credentials, so JavaScript can draw the image to a readable canvas. The endpoint remains public; this does not enable browser access to authenticated visual or dashboard routes. Pages using Cross-Origin-Embedder-Policy: require-corp should use the CORS-enabled form; plain no-CORS images in that mode are not separately guaranteed.
Successful captures are cache-first while their S3 object exists. To explicitly refresh a stored screenshot, add force=true; that request consumes one daily capture and returns Cache-Control: no-store.
Customizable Options
curl "https://snap.i2dev.com/v1/screenshot?url=https%3A%2F%2Fexample.com&format=jpeg&width=1920&height=1080"Available parameters, listed in the order a canonical query string must use them (see the caching note below):
url(required) β The page to screenshotformatβpngorjpeg(default: jpeg; the authenticated API defaults to png)widthβ Viewport width (default: 1280)heightβ Viewport height (default: 720)fullPageβ Capture the full scrollable page. Acceptstrue/1andfalse/0; omitting it meansfalsequalityβ JPEG quality, 1β100 (default: 80). Ignored forformat=png, which is losslesswaitUntilβ Wait forload,domcontentloaded, ornetworkidlewaitForSelectorβ Wait for a CSS selector before capturetimezoneIdandlocaleβ Stabilize regional renderingtimeoutMsandnavTimeoutMsβ Set the overall render and navigation timeouts, up to the server maximumforceβ Explicitly refresh an existing screenshot; consumes one daily capture and is never cached
Responses are served from the CDN only when the query string is in its canonical form: the parameters above (excluding timeoutMs, navTimeoutMs, and force), each at most once, in the order listed. Any other spelling β a reordered key, a padded number, a percent-encoded parameter name, or a timeout parameter β still returns the same image from the same stored object, but with Cache-Control: no-store. Omitting a parameter you are leaving at its default is the shortest canonical form and resolves to the same stored screenshot as spelling it out.
The interactive builder on the screenshot API page composes these parameters for you, and its own page URL updates to match, so copying the address bar shares any configuration.
Anonymous Limits
Anonymous requests are limited to 30 screenshots per day per IP, resetting at midnight UTC. To prevent rotating cloud or serverless egress from bypassing that limit, each network ASN also shares a high 10,000-attempt daily cost ceiling. This shared ceiling is intentionally much higher because a network can contain many unrelated users. Cache hits do not consume either daily quota but remain subject to the per-IP rate limit. For reliable server-side volume, higher limits, and additional keyed features, authenticate with an API key.
Per-minute throttling returns HTTP 429 with rate_limited, X-Snap-Rate-Limit-Scope: anonymous_ip_minute, and a Retry-After delay until the next token is available. Daily IP or ASN quota exhaustion also returns HTTP 429, but uses quota_exceeded, X-Snap-Quota-Scope, and a Retry-After delay until midnight UTC. Both responses retain the standard limit-reached PNG or JPEG body. CORS-mode JavaScript can read the four standard diagnostic headers plus the applicable retry and rate/quota scope headers; those three limit headers appear only on 429 limit responses.
Other anonymous failures continue to return HTTP 200 with a standard 1280x720 placeholder so image embeds remain renderable. Inspect X-Snap-Result, X-Snap-Error-Code, X-Snap-Error-Status, X-Snap-Request-Id for machine-readable diagnostics. Every error placeholder uses Cache-Control: no-store.
Authenticated API
For production use, CI/CD integration, and higher rate limits, use the authenticated API.
curl -X POST https://snap.i2dev.com/v1/screenshots \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"viewport": { "width": 1280, "height": 720 }
}'The response includes the screenshot URL and metadata:
{
"jobId": "...",
"request_id": "...",
"imageUrl": "https://snap-i2dev-com.s3.amazonaws.com/...?X-Amz-...",
"contentType": "image/png",
"bytes": 12345,
"durationMs": 987
}Authentication
All authenticated API requests require an API key:
Authorization: Bearer YOUR_API_KEYYou can also use x-api-key if preferred:
x-api-key: YOUR_API_KEYError Codes
The authenticated API returns structured error responses with consistent HTTP statuses and codes:
| Code | HTTP Status | Description |
|---|---|---|
| invalid_url | 400 | URL is malformed or uses an unsupported protocol |
| unsafe_url | 400 | URL resolves to a private/internal IP address |
| invalid_render_option | 400 | Chromium rejected the supplied timezone or locale |
| unauthorized | 401 | Missing or invalid API key |
| rate_limited | 429 | Too many requests. Check Retry-After header |
| navigation_timeout | 504 | Page failed to load within timeout |
| render_blocked | 422 | Target site blocked automated rendering |
| automation_refused | 502 | Target site rejected the headless browser |
| upstream_unreachable | 502 | Target site could not be loaded |
Authenticated API error responses include a request_id for debugging:
{
"error": "URL resolves to a private IP address",
"code": "unsafe_url",
"request_id": "req_xyz789"
}