{"service":"rerun-shot","summary":"URL in, image out. A screenshot API on top of Playwright and headless chromium.","baseUrl":"http://shot.rerun.build","auth":{"required":true,"how":"Send `Authorization: Bearer <key>` or `?key=<key>` on every /screenshot request."},"endpoints":[{"method":"GET","path":"/screenshot","summary":"Capture a URL. Parameters go in the query string, so the endpoint drops straight into an <img src> or an og:image tag.","returns":"image/png, image/jpeg or image/webp"},{"method":"POST","path":"/screenshot","summary":"Same capture, same parameters, as a JSON body.","returns":"image/png, image/jpeg or image/webp"},{"method":"GET","path":"/health","summary":"Liveness plus browser, queue and cache counters.","returns":"application/json"},{"method":"GET","path":"/docs","summary":"This document. Add `?format=md`, or send `Accept: text/markdown`, for the markdown rendering.","returns":"application/json"},{"method":"GET","path":"/","summary":"Same document as /docs.","returns":"application/json"}],"parameters":[{"name":"url","required":true,"type":"string","description":"http or https page to capture"},{"name":"width","required":false,"type":"number","integer":true,"min":16,"max":4000,"default":1200,"description":"viewport width in CSS pixels"},{"name":"height","required":false,"type":"number","integer":true,"min":16,"max":4000,"default":630,"description":"viewport height in CSS pixels"},{"name":"scale","required":false,"type":"number","min":0.1,"max":4,"default":1,"description":"device pixel ratio: 1200x630 at scale=2 returns a 2400x1260 image"},{"name":"format","required":false,"type":"enum","values":["png","jpeg","webp"],"default":"png","description":"output image format"},{"name":"quality","required":false,"type":"number","integer":true,"min":1,"max":100,"default":85,"description":"encoder quality, used by jpeg and webp only"},{"name":"full_page","required":false,"type":"boolean","default":false,"description":"capture the whole scrollable document instead of the viewport"},{"name":"selector","required":false,"type":"string","description":"capture this single element instead of the viewport"},{"name":"wait_for","required":false,"type":"string","description":"CSS selector to wait for before shooting, e.g. [data-og-ready=\"true\"]"},{"name":"wait_until","required":false,"type":"enum","values":["load","domcontentloaded","networkidle","commit"],"default":"networkidle","description":"navigation milestone to wait for"},{"name":"delay","required":false,"type":"number","integer":true,"min":0,"max":10000,"default":0,"description":"extra milliseconds to wait before shooting"},{"name":"dark","required":false,"type":"boolean","default":true,"description":"emulated prefers-color-scheme"},{"name":"reduced_motion","required":false,"type":"boolean","default":true,"description":"emulated prefers-reduced-motion, so a page that honours it stops animating"},{"name":"freeze","required":false,"type":"boolean","default":false,"description":"force every animation to pause and every transition off, for pages that ignore reduced motion"},{"name":"transparent","required":false,"type":"boolean","default":false,"description":"drop the page background, png only"},{"name":"css","required":false,"type":"string","description":"CSS injected after load, before the shot"},{"name":"js","required":false,"type":"string","description":"JavaScript evaluated after load, before the shot"},{"name":"timeout","required":false,"type":"number","integer":true,"min":1000,"max":120000,"default":30000,"description":"milliseconds any single navigation or wait may take"},{"name":"cache","required":false,"type":"boolean","default":true,"description":"read from and write to the disk cache, cache=0 forces a fresh capture"}],"responseHeaders":{"X-Cache":"HIT when the image came from the disk cache, MISS when it was captured now","X-Render-Ms":"milliseconds spent serving this request","X-Shot-Key":"the cache key, stable for a given set of pixel-affecting options","Cache-Control":"public, max-age=86400"},"errors":[{"status":400,"code":"invalid_options","meaning":"a parameter failed validation, `issues` lists the offenders"},{"status":400,"code":"invalid_url","meaning":"`url` is not a valid http or https URL"},{"status":400,"code":"dns_error","meaning":"the target hostname does not resolve"},{"status":400,"code":"host_not_allowed","meaning":"the target is link-local, outside ALLOWED_HOSTS, or private on an unauthenticated request"},{"status":401,"code":"unauthorized","meaning":"missing or wrong API key"},{"status":502,"code":"navigation_failed","meaning":"the browser got no response from the target"},{"status":502,"code":"upstream_error","meaning":"the target page answered 4xx or 5xx"},{"status":503,"code":"overloaded","meaning":"the capture queue is full, retry shortly"},{"status":504,"code":"timeout","meaning":"a navigation or a wait ran past `timeout`"},{"status":500,"code":"internal_error","meaning":"unexpected failure, the message carries the detail"}],"limits":{"maxConcurrency":2,"maxQueue":24,"cacheTtlMs":86400000,"cacheDisabled":false,"allowedHosts":[],"bodyLimitBytes":1000000},"notes":["A successful capture returns raw image bytes, not JSON. Only errors have a JSON body, shaped `{ error, message }` (`{ error, issues }` for `invalid_options`).","Booleans accept `1`, `true` or `yes` for true; any other value is false. An absent or empty parameter takes the default.","Use `POST` with a JSON body when `css` or `js` is long enough to strain a query string; the parameters are identical either way. Body limit is 1 MB.","`selector` wins over `full_page`: an element capture is never a full-page capture.","`quality` is ignored for `png`, and `transparent` is ignored for every format but `png`.","`width` and `height` multiplied by `scale` must each stay under 8000px.","Every capture waits for `document.fonts.ready` and one painted frame before shooting, so text is never captured mid-swap.","The cache key is a hash of everything that changes the pixels, so `timeout` and `cache` do not affect it. To refresh an image when its source data changes, put a version in the URL (`?v=<hash>`) rather than wait out the TTL.","Identical requests arriving together share one capture, so fanning out the same URL is cheap.","Loopback and RFC1918 targets (`http://localhost:3000/og`) are allowed for a request authenticated with the API key, and refused for an anonymous one in production. Link-local addresses are refused either way."],"recipes":[{"title":"Open Graph card","request":"curl -o og.png \"http://shot.rerun.build/screenshot?url=https://rerun.build&width=1200&height=630\""},{"title":"Retina JPEG, light mode","request":"curl -o card.jpg \"http://shot.rerun.build/screenshot?url=https://rerun.build&scale=2&format=jpeg&quality=90&dark=0\""},{"title":"One element, once the page says it is ready","request":"curl -o card.png \"http://shot.rerun.build/screenshot?url=https://rerun.build/card&selector=%23card&wait_for=%5Bdata-og-ready%3D%22true%22%5D\""},{"title":"Whole page as webp","request":"curl -o page.webp \"http://shot.rerun.build/screenshot?url=https://rerun.build&full_page=1&format=webp&quality=80\""},{"title":"POST, for css and js too long to put in a query string","request":"curl -o shot.png -X POST \"http://shot.rerun.build/screenshot\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"url\":\"https://rerun.build\",\"css\":\"header{display:none}\",\"js\":\"document.title='\\''ready'\\''\",\"freeze\":true}'"},{"title":"Bypass the cache","request":"curl -o fresh.png \"http://shot.rerun.build/screenshot?url=https://rerun.build&cache=0\""}]}