# Introduction > shotkit renders any URL, HTML or Markdown to images, PDFs, videos or Markdown with one HTTP request. shotkit is a screenshot API. One HTTP request turns a URL, or your own HTML or Markdown, into an image (PNG, JPG, WebP, AVIF or TIFF), a PDF, a recording (MP4, WebM, or animated GIF or WebP), or the page's content as clean HTML or Markdown. You can capture the full page or a single element, at any viewport size or with one of 153 device presets, and remove cookie banners, ads and chat widgets first. Typical uses are social cards and Open Graph images, invoices and reports, visual regression tests, archiving pages, and feeding web pages to AI agents. ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://github.com","format":"png","full_page":true,"block_cookie_banners":true,"dark_mode":true}' \ --fail-with-body -o shot.png ``` ## The endpoint Everything goes through one endpoint: ```text https://shotkit.net/api/take ``` - `POST` with a JSON body, as in the example above. This is the easiest way to call it from code. - `GET` with the same options as query parameters, so a URL alone is a complete request. Use it to embed renders in `` (see [signed URLs](https://shotkit.net/docs/authentication.md#signed-urls)). Requests are authenticated with an [access key](https://shotkit.net/docs/authentication.md). The response is the file itself by default, or JSON with [`response_type=json`](https://shotkit.net/docs/responses.md). ## What you can do - **Screenshots** of any page, in PNG, JPG, WebP, AVIF or TIFF, at any viewport size or with one of 153 [device presets](https://shotkit.net/docs/options/viewport.md#viewport_device), including [full page](https://shotkit.net/docs/full-page.md). - **[Clean captures](https://shotkit.net/docs/clean-screenshots.md)** without cookie banners, ads, trackers or chat widgets, plus custom CSS, JavaScript, clicks and hovers. - **[HTML and Markdown in](https://shotkit.net/docs/html-and-markdown.md)**, for social cards, certificates and invoices; **HTML and Markdown out**, for LLMs and [agents](https://shotkit.net/docs/ai-agents.md). - **[PDFs](https://shotkit.net/docs/pdf.md)** with paper sizes, margins and one-page fitting. - **[Videos and animations](https://shotkit.net/docs/video.md)** as MP4, WebM, GIF or WebP, including scrolling recordings. - **[Caching](https://shotkit.net/docs/caching.md)**, **[async renders with webhooks](https://shotkit.net/docs/async-and-webhooks.md)** and **[metadata](https://shotkit.net/docs/responses.md#metadata)** for production use. All 107 options are listed in the [option reference](https://shotkit.net/docs/options.md), each with an example. ## For agents These docs are available as Markdown. Add `.md` to any docs URL (for example [/docs/quickstart.md](https://shotkit.net/docs/quickstart.md)), or request a page with `Accept: text/markdown`. [/llms.txt](https://shotkit.net/llms.txt) lists every page, and [/llms-full.txt](https://shotkit.net/llms-full.txt) has all of them in one file. See [AI agents](https://shotkit.net/docs/ai-agents.md). ## Next steps 1. [Quickstart](https://shotkit.net/docs/quickstart.md): get a key and take your first screenshot. 2. [Option reference](https://shotkit.net/docs/options.md): everything you can change. 3. [Errors](https://shotkit.net/docs/errors.md) and [limits](https://shotkit.net/docs/limits.md): what can go wrong, and how much you can render. --- # Quickstart > Get an access key and take your first screenshot in a couple of minutes. ## 1. Get an access key [Create an account](https://shotkit.net/login) and verify your email address. Your access key is on the [API keys](https://shotkit.net/keys) page. The free Hobby plan includes 100 screenshots a month, no card required. Keys belong to your workspace: everyone in it shares the same keys, plan and quota. ## 2. Take a screenshot Pass your key in the `X-Access-Key` header and the page in `url`. The response body is the image. Pick your language; every example on this site follows your choice. ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"png"}' \ --fail-with-body -o shot.png ``` Replace `YOUR_ACCESS_KEY` with your key. With no other options you get a 1280×1024 screenshot of the viewport after the page has loaded. The same request works as a plain URL, with the key in the `access_key` parameter. You can open it in a browser: ```text https://shotkit.net/api/take?access_key=YOUR_ACCESS_KEY&url=https://example.com&format=png ``` ## 3. Add options Options change how the page is loaded and captured. This one captures the full page on a retina display, in dark mode, without the cookie banner: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://github.com","format":"png","full_page":true,"device_scale_factor":2,"dark_mode":true,"block_cookie_banners":true}' \ --fail-with-body -o shot.png ``` Every option has a default, so you only send what you want to change. Unknown options are rejected, so a typo fails loudly instead of being ignored. ## 4. Handle errors Errors are JSON with an HTTP status of 400 or above: ```json { "is_successful": false, "error_code": "host_returned_error", "error_message": "The site returned HTTP 404. Set ignore_host_errors=true to capture anyway.", "returned_status_code": 404 } ``` Check the status before saving the body. The snippets above do. Failed renders don't count toward your monthly screenshots. All codes are listed in [Errors](https://shotkit.net/docs/errors.md). ## Try it in the playground The [playground](https://shotkit.net/playground) has every option as a form with a live preview, and generates the code for your request in 12 languages. Its URL holds your setup, so you can share it. ## Next steps - [Authentication](https://shotkit.net/docs/authentication.md): access keys, secret keys and signed URLs. - [Responses](https://shotkit.net/docs/responses.md): return JSON with a file URL and metadata instead of the file. - [Option reference](https://shotkit.net/docs/options.md): all 107 options. --- # Authentication > Authenticate with an access key, and sign GET URLs and verify webhooks with your secret key. Each workspace has two keys, both on the [API keys](https://shotkit.net/keys) page: | Key | Looks like | Used for | | --- | --- | --- | | Access key | `ak_…` | Identifies your workspace on every request. | | Secret key | `sk_…` | Signs GET URLs and webhook deliveries. Never send it in a request. | ## Access key Send it in the `X-Access-Key` header: ```bash curl "https://shotkit.net/api/take?url=https://example.com" -H "X-Access-Key: YOUR_ACCESS_KEY" -o shot.jpg ``` or as the `access_key` parameter, in the query string or the JSON body: ```text https://shotkit.net/api/take?access_key=YOUR_ACCESS_KEY&url=https://example.com ``` A missing or wrong key returns `401` with `access_key_invalid`. On the free Hobby plan, keys only work once someone in the workspace has verified their email address; until then requests return `403` with `email_not_verified`. Keep the access key on your server. Anyone who has it can render screenshots on your quota. ## Signed requests A signature proves a request was made by someone who has your secret key, and that nobody changed it afterwards. It's the hex HMAC-SHA256 of the request, keyed with your secret key: | Request | What you sign | Where the signature goes | | --- | --- | --- | | `GET` | The query string, exactly as sent, without `signature` | `signature` query parameter | | `POST` | The raw JSON body, exactly as sent | `X-Signature` header | Signatures are optional unless you [require them](#require-signed-requests). A signature that is sent is always checked: a mismatch returns `403` with `signature_is_not_valid`. ### Signed URLs A `GET` URL with your access key in it can go straight into an ``, an email or a page. Sign it so nobody can change its parameters: 1. Build the query string with every parameter, including `access_key`. 2. Compute `HMAC-SHA256(query, secret_key)` as lowercase hex. 3. Append `&signature=`. Sign the string exactly as it appears in the URL, after encoding, and don't reorder or re-encode parameters afterwards. ```js tab="Node.js" import { createHmac } from "node:crypto"; const query = new URLSearchParams({ access_key: process.env.SHOTKIT_ACCESS_KEY, url: "https://example.com", format: "png", }).toString(); const signature = createHmac("sha256", process.env.SHOTKIT_SECRET_KEY).update(query).digest("hex"); const src = `https://shotkit.net/api/take?${query}&signature=${signature}`; ``` ```python tab="Python" import hashlib, hmac, os from urllib.parse import urlencode query = urlencode({ "access_key": os.environ["SHOTKIT_ACCESS_KEY"], "url": "https://example.com", "format": "png", }) signature = hmac.new(os.environ["SHOTKIT_SECRET_KEY"].encode(), query.encode(), hashlib.sha256).hexdigest() src = f"https://shotkit.net/api/take?{query}&signature={signature}" ``` ```php tab="PHP" getenv("SHOTKIT_ACCESS_KEY"), "url" => "https://example.com", "format" => "png", ]); $signature = hash_hmac("sha256", $query, getenv("SHOTKIT_SECRET_KEY")); $src = "https://shotkit.net/api/take?$query&signature=$signature"; ``` ```ruby tab="Ruby" require "openssl" require "uri" query = URI.encode_www_form( access_key: ENV["SHOTKIT_ACCESS_KEY"], url: "https://example.com", format: "png", ) signature = OpenSSL::HMAC.hexdigest("SHA256", ENV["SHOTKIT_SECRET_KEY"], query) src = "https://shotkit.net/api/take?#{query}&signature=#{signature}" ``` ```go tab="Go" query := url.Values{ "access_key": {os.Getenv("SHOTKIT_ACCESS_KEY")}, "url": {"https://example.com"}, "format": {"png"}, }.Encode() mac := hmac.New(sha256.New, []byte(os.Getenv("SHOTKIT_SECRET_KEY"))) mac.Write([]byte(query)) signature := hex.EncodeToString(mac.Sum(nil)) src := "https://shotkit.net/api/take?" + query + "&signature=" + signature ``` Signed URLs work well with [caching](https://shotkit.net/docs/caching.md): the first view renders, and later views of the same URL are redirected to the cached file for free. ### Signed POST requests Serialize the body once, sign that string, and send the same string: ```js tab="Node.js" import { createHmac } from "node:crypto"; const body = JSON.stringify({ url: "https://example.com", format: "png" }); const signature = createHmac("sha256", process.env.SHOTKIT_SECRET_KEY).update(body).digest("hex"); const res = await fetch("https://shotkit.net/api/take", { method: "POST", headers: { "X-Access-Key": process.env.SHOTKIT_ACCESS_KEY, "X-Signature": signature, "Content-Type": "application/json", }, body, }); ``` ```python tab="Python" import hashlib, hmac, json, os import requests body = json.dumps({"url": "https://example.com", "format": "png"}) signature = hmac.new(os.environ["SHOTKIT_SECRET_KEY"].encode(), body.encode(), hashlib.sha256).hexdigest() res = requests.post( "https://shotkit.net/api/take", headers={ "X-Access-Key": os.environ["SHOTKIT_ACCESS_KEY"], "X-Signature": signature, "Content-Type": "application/json", }, data=body, timeout=90, ) ``` ```bash tab="cURL" BODY='{"url":"https://example.com","format":"png"}' SIGNATURE=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SHOTKIT_SECRET_KEY" -hex | sed 's/^.* //') curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: $SHOTKIT_ACCESS_KEY" \ -H "X-Signature: $SIGNATURE" \ -H "Content-Type: application/json" \ -d "$BODY" -o shot.png ``` Don't let your HTTP client re-serialize the body (in Python, pass `data=body`, not `json=`), or the bytes it sends won't match the signature. ### Require signed requests An access key alone is enough to render, so a key that leaks, for example from a signed URL in a public page, can be used for any request on your quota. To prevent that, owners and admins can turn on **Require signed requests** on the [API keys](https://shotkit.net/keys) page. Then every request without a signature fails with `403` and `signature_is_required`, and a leaked key can only replay URLs you've already signed. Before turning it on, make sure all your code signs its requests. The playground signs its own renders, and its **Signed** switch generates signed code in every language, plus a signed GET URL. The setting takes up to a minute to apply. ## Secret key The secret key also signs [webhook deliveries](https://shotkit.net/docs/async-and-webhooks.md#verify-the-signature), so you can check that a POST to your webhook came from shotkit. Keep it on your server. --- # HTML and Markdown > Render your own HTML or Markdown to images and PDFs, and turn any web page into HTML or Markdown. ## Render HTML Send `html` instead of `url` to render your own markup. This is how you generate Open Graph images, certificates, receipts and invoices from a template. ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"html":"
Hello, world
","viewport_width":1200,"viewport_height":630,"format":"png"}' \ --fail-with-body -o shot.png ``` Set the viewport to the size you want the image to be, or capture one element with `selector` (see below). Things to know: - The page has no origin, so relative URLs don't resolve. Use absolute URLs for images, stylesheets and fonts, or inline them as `data:` URLs. - External resources such as Google Fonts load normally. Wait for them with `wait_until=networkidle0` if they arrive late. - [Cookies](https://shotkit.net/docs/options/request.md#cookies) need an explicit `Domain`. - Use `POST` for anything but tiny snippets. Long HTML in a query string runs into URL length limits. ### Capture just the element To get an image exactly the size of your card, capture it with `selector`, and make the background transparent with `omit_background` (PNG only): ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"html":"
Passing
","selector":".badge","format":"png","omit_background":true,"device_scale_factor":2}' \ --fail-with-body -o shot.png ``` ### HTML to PDF Combine `html` with `format=pdf` for invoices and reports. See [PDF](https://shotkit.net/docs/pdf.md). ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"html":"

Invoice #1042

Total: $90.00

","format":"pdf","pdf_paper_format":"a4","pdf_margin":"2cm","pdf_print_background":true}' \ --fail-with-body -o shot.pdf ``` ## Render Markdown Send `markdown` to render it with a clean, readable stylesheet: headings, lists, tables, code and images. ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"markdown":"# Release notes\n\n- Faster renders\n- Video output\n- Cookie banner blocking","format":"png","viewport_width":800,"viewport_height":400}' \ --fail-with-body -o shot.png ``` ## Get HTML or Markdown out With `format=html` or `format=markdown`, you get the page's content as text instead of an image. The page is loaded and rendered in a real browser first, so content added by JavaScript is included. ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"markdown","block_cookie_banners":true}' \ --fail-with-body -o shot.md ``` Markdown output strips scripts and styles and is a compact way to feed pages to an LLM (see [AI agents](https://shotkit.net/docs/ai-agents.md)). Every other option still applies: `selector` returns only that element, `click` and `scripts` run first, and blocking removes clutter. | Option | Effect on HTML/Markdown output | | --- | --- | | `selector` | Returns only the matching element. | | `include_shadow_dom` | Includes the content of shadow roots (web components). | | `wait_for_selector` | Waits for client-rendered content before reading it. | ### A screenshot and the content in one request `metadata_content` stores the page's HTML or Markdown next to the screenshot and returns its URL: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","metadata_content":true,"metadata_content_format":"markdown"}' \ --fail-with-body -o shot.json ``` ```json { "screenshot_url": "https://shotkit.net/cdn/files/…/….jpg", "content": { "url": "https://shotkit.net/cdn/files/…/….md", "expires": "Fri, 09 Oct 2026 16:00:00 GMT", "format": "markdown" } } ``` --- # Full-page screenshots > Capture the whole page with lazy-loaded content, cap its height, split it into slices, or capture one element. ## Capture the whole page `full_page=true` captures everything from the top of the page to the bottom, not just the viewport: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true}' \ --fail-with-body -o shot.jpg ``` Before capturing, the page is scrolled to the bottom in viewport-sized steps and back to the top. That triggers lazy-loaded images, infinite-scroll sections and scroll animations, which otherwise show up blank. ### Tune the scrolling | Option | Default | Use it when | | --- | --- | --- | | `full_page_scroll_delay` | `400` ms | Content loads slowly after scrolling into view. | | `full_page_scroll_by` | viewport height | Lazy loading is triggered by smaller steps. | | `full_page_scroll` | on with `full_page` | Set `false` to skip scrolling for a faster capture of static pages. | ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true,"full_page_scroll_by":500,"full_page_scroll_delay":800}' \ --fail-with-body -o shot.jpg ``` Scrolling stops at 50,000 px, so infinite feeds can't scroll forever. ### Cap the height Some pages are extremely long. `full_page_max_height` cuts the capture at a height in CSS pixels: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true,"full_page_max_height":6000}' \ --fail-with-body -o shot.jpg ``` ## Split into slices Very tall images are hard to view and some tools can't open them. `full_page_slices` also cuts the capture into slices of `full_page_slice_height` (default 4000 px), each uploaded as its own file: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true,"full_page_slices":true,"full_page_slice_height":2000,"full_page_slice_overlap_height":100,"response_type":"json"}' \ --fail-with-body -o shot.json ``` ```json { "screenshot_url": "https://shotkit.net/cdn/files/…/full.jpg", "slices": [ { "index": 0, "offset_y": 0, "width": 1280, "height": 2000, "url": "https://shotkit.net/cdn/files/…/0.jpg" }, { "index": 1, "offset_y": 1900, "width": 1280, "height": 2000, "url": "https://shotkit.net/cdn/files/…/1.jpg" } ] } ``` `offset_y` is where the slice starts in the full image. With an overlap, consecutive slices share that many pixels so nothing is lost at the cut. Without `response_type=json`, the full image is returned and the `X-Full-Page-Slices-Url` header points to a JSON file with the same list. ## Capture one element `selector` captures a single element, scrolled into view first: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","selector":"main"}' \ --fail-with-body -o shot.jpg ``` If the element is taller than the viewport, it's captured in full. Set `capture_beyond_viewport=false` to clip it to the viewport. If nothing matches, the viewport is captured, unless you set `error_on_selector_not_found=true`. ## Capture a rectangle `clip_x`, `clip_y`, `clip_width` and `clip_height` capture an area of the page, in CSS pixels from the top-left of the document: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","clip_x":0,"clip_y":0,"clip_width":1280,"clip_height":640}' \ --fail-with-body -o shot.jpg ``` ## Retina and smaller files `device_scale_factor` multiplies the resolution (up to 5). `image_width` and `image_height` scale the result down afterwards, keeping the aspect ratio, so you can capture at 2× and deliver a sharp thumbnail: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true,"device_scale_factor":2,"image_width":800,"format":"avif","image_quality":60}' \ --fail-with-body -o shot.avif ``` --- # Clean screenshots > Remove cookie banners, ads, trackers and chat widgets, hide elements, inject CSS and JavaScript, click and wait. ## Remove the clutter Turn on the blockers you need: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","block_cookie_banners":true,"block_ads":true,"block_trackers":true,"block_chats":true}' \ --fail-with-body -o shot.jpg ``` | Option | What it does | | --- | --- | | `block_cookie_banners` | Blocks consent-manager scripts (OneTrust, Cookiebot, Didomi, Usercentrics, …), hides their elements, and re-enables scrolling they lock. | | `block_banners_by_heuristics` | Also removes fixed or sticky overlays that look like banners, modals or newsletter popups. Aggressive, so check the result. | | `block_ads` | Blocks requests to ad networks. | | `block_trackers` | Blocks analytics and tracking scripts. Pages often load faster too. | | `block_chats` | Blocks and hides chat widgets (Intercom, Drift, Crisp, HubSpot, …). | Blocked requests never load, so nothing flashes on screen and nothing is left to hide. ### Block anything else `block_requests` takes wildcard patterns matched against request URLs, and `block_resources` blocks whole resource types: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","block_requests":["*.doubleclick.net/*","*/analytics.js"],"block_resources":["media","font"]}' \ --fail-with-body -o shot.jpg ``` Resource types: `document`, `stylesheet`, `image`, `media`, `font`, `script`, `texttrack`, `xhr`, `fetch`, `eventsource`, `websocket`, `manifest`, `other`. The page itself is never blocked. ## Hide or restyle elements `hide_selectors` hides every element matching any selector. `styles` injects any CSS: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","hide_selectors":[".newsletter-popup","#promo-bar"],"styles":"header { position: static !important } body { font-size: 18px }"}' \ --fail-with-body -o shot.jpg ``` ## Run JavaScript `scripts` runs in the page after it loads, before the capture. Use it to open sections, fill in demo data or remove things CSS can't reach: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","scripts":"document.querySelectorAll('\''details'\'').forEach(d => d.open = true)"}' \ --fail-with-body -o shot.jpg ``` If the script throws, the request fails with `script_triggers_error`. If it navigates (submitting a form, following a link), set `scripts_wait_until` so the capture waits for the next page. On sites with a strict Content-Security-Policy, scripts that load other scripts may need `bypass_csp=true`. ## Click and hover `click` and `hover` act on the first visible element matching a selector, after scripts: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","click":"button.load-more","delay":1}' \ --fail-with-body -o shot.jpg ``` By default a missing element fails the request with `selector_not_found`. Set `error_on_click_selector_not_found=false` (or the `hover` equivalent) for elements that are only sometimes there, like a promo popup. ## Wait for the right moment By default the capture happens after the `load` event, once the network has been briefly idle, the page has stopped changing, and fonts and visible images have loaded (each capped at a few seconds). For pages that render even later: | Option | Waits for | | --- | --- | | `wait_until` | `load`, `domcontentloaded`, `networkidle0` (no requests for 500 ms) or `networkidle2` (at most 2). | | `wait_for_selector` | An element to appear, such as a chart rendered by JavaScript. | | `delay` | A fixed number of seconds, for animations. | ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","wait_until":["networkidle0"],"wait_for_selector":".chart canvas"}' \ --fail-with-body -o shot.jpg ``` Prefer `wait_for_selector` over `delay`: it's as fast as the page allows and fails clearly with `selector_not_found` if the content never shows up. ## Freeze animations `reduce_motion=true` finishes CSS animations and transitions and pauses videos, so hero sections aren't caught half-faded. `dark_mode=true` renders sites that support it in dark mode. ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","reduce_motion":true,"dark_mode":true}' \ --fail-with-body -o shot.jpg ``` ## Fail instead of capturing a broken page When screenshots feed something automated, a picture of an error page is worse than an error. These options fail the request instead: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/product/42","fail_if_content_missing":["Add to cart"],"fail_if_content_contains":["Access denied"],"fail_if_request_failed":["*api.example.com*"]}' \ --fail-with-body -o shot.jpg ``` A 4xx or 5xx from the page itself already fails with `host_returned_error` unless `ignore_host_errors=true`. Failed requests don't count toward your monthly screenshots. --- # PDF > Convert web pages or your own HTML to PDF with paper sizes, margins, backgrounds and one-page fitting. ## URL to PDF Set `format=pdf`: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf"}' \ --fail-with-body -o shot.pdf ``` The page is printed the way Chrome prints it: text stays selectable and links stay clickable. Sites with print stylesheets use them. To get a PDF that looks like the screen instead, print backgrounds and use the screen media type: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","pdf_print_background":true,"media_type":"screen"}' \ --fail-with-body -o shot.pdf ``` ## Paper and margins | Option | Default | Values | | --- | --- | --- | | `pdf_paper_format` | `letter` | `a0`–`a6`, `letter`, `legal`, `tabloid` | | `pdf_landscape` | `false` | | | `pdf_margin` | `0` | Any CSS length: `20px`, `1cm`, `0.5in` | | `pdf_margin_top`, `_right`, `_bottom`, `_left` | `pdf_margin` | Override one side. | ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","pdf_paper_format":"a4","pdf_margin":"1.5cm","pdf_margin_top":"2.5cm"}' \ --fail-with-body -o shot.pdf ``` ## One long page `pdf_fit_one_page=true` puts the whole page on a single PDF page sized to the content, with no page breaks. Good for archiving and sharing a page as it looks on screen: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","pdf_fit_one_page":true,"pdf_print_background":true}' \ --fail-with-body -o shot.pdf ``` ## HTML to PDF For invoices, receipts and reports, render your own template. Use CSS for page layout: `@page` rules, `break-before: page` and `break-inside: avoid` all work. ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"html":"

Invoice #1042

Pro plan$90.00

Total: $90.00

","format":"pdf","pdf_paper_format":"a4","pdf_margin":"2cm"}' \ --fail-with-body -o shot.pdf ``` Everything that changes the page also works for PDFs: [blocking](https://shotkit.net/docs/clean-screenshots.md), `styles`, `scripts`, `hide_selectors`, cookies and waits. --- # Video and animated GIF > Record pages as MP4, WebM, animated GIF or animated WebP, including smooth scroll-through videos. ## Record a page Set `format` to `mp4`, `webm` or `gif`, and the page is recorded in real time instead of captured once. `webp` is a still image by default and is recorded as an animated WebP when you set any animation option (`animation_duration`, `animation_fps` or `animation_scroll`). ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"mp4","animation_duration":5}' \ --fail-with-body -o shot.mp4 ``` | Option | Default | Range | | --- | --- | --- | | `animation_duration` | `3` seconds | 0.5–20 | | `animation_fps` | 30 for video, 10 for GIF/WebP | 1–30 | | `animation_scroll` | `false` | Scroll to the bottom while recording. | | `image_quality` | `80` | Video bitrate for MP4/WebM. | Recording starts once the page is ready, after clicks, scripts and waits, so you can record exactly the state you set up. ## Scroll-through videos `animation_scroll=true` scrolls smoothly from the top to the bottom over the duration. It's the recorded counterpart of [full page](https://shotkit.net/docs/full-page.md) (`full_page` itself isn't supported for recorded formats): ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"mp4","animation_scroll":true,"animation_duration":10,"block_cookie_banners":true}' \ --fail-with-body -o shot.mp4 ``` ## Animated GIF and WebP GIF and animated WebP play everywhere images do: READMEs, emails, chat. Keep them short and small; lower `animation_fps` and scale down with `image_width`. For WebP, setting `animation_duration` is what makes it animated: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"gif","animation_duration":4,"animation_fps":12,"image_width":640}' \ --fail-with-body -o shot.gif ``` An animated WebP is usually much smaller than a GIF at the same quality: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"webp","animation_duration":4,"image_quality":70,"image_width":640}' \ --fail-with-body -o shot.webp ``` ## Record one element `selector` (or a [clip](https://shotkit.net/docs/options/clip.md)) crops the recording to an element, for example an animated chart or a component demo: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"webm","selector":".hero","animation_duration":4}' \ --fail-with-body -o shot.webm ``` The element must be inside the viewport while recording; it's scrolled into view first. ## Things to know - Recordings are made at 1× resolution; `device_scale_factor` doesn't apply. - Recording takes real time, so a 20-second video takes at least 20 seconds. For long recordings, use [async](https://shotkit.net/docs/async-and-webhooks.md). --- # Responses and metadata > Get the file, JSON with a file URL, or nothing, plus metadata such as the page title, Open Graph tags and HTTP status. ## Response types `response_type` decides what a successful request returns: | Value | Returns | | --- | --- | | `by_format` (default) | The file itself, with its `Content-Type` (`image/png`, `application/pdf`, …). | | `json` | JSON with the URL of the uploaded file and any metadata you asked for. | | `empty` | `200` with no body. Useful to warm the [cache](https://shotkit.net/docs/caching.md). | ### The file The body is the file. Metadata, if requested, comes as response headers. `attachment_name` makes browsers download it under that name: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","attachment_name":"example"}' \ --fail-with-body -o shot.pdf ``` Response headers: | Header | Value | | --- | --- | | `Content-Type` | The format's media type. | | `X-Render-Time-Ms` | Time spent on the request, in milliseconds. | | `Content-Disposition` | `attachment; filename="example.pdf"` with `attachment_name`. | | `X-Cache`, `X-Cache-Url` | With `cache=true`: `HIT` or `MISS`, and the cached file's URL. | | `X-Page-Title`, `X-Open-Graph`, … | Metadata (see below), as URL-encoded JSON. | ### JSON With `response_type=json`, the file is uploaded and you get its URL: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","metadata_page_title":true,"metadata_image_size":true}' \ --fail-with-body -o shot.json ``` ```json { "screenshot_url": "https://shotkit.net/cdn/files/…/….jpg", "metadata": { "page_title": "Example Domain", "image_size": { "width": 1280, "height": 1024 } } } ``` Files stay available for `cache_ttl` seconds (4 hours by default, up to 30 days), then they're deleted. Download them if you need them longer. ## Metadata Turn on what you need. Each adds a field to `metadata` in JSON, or an `X-…` header otherwise. | Option | JSON field | Header | | --- | --- | --- | | `metadata_image_size` | `metadata.image_size`: `{ width, height }` of the output | `X-Image-Size` | | `metadata_page_title` | `metadata.page_title` | `X-Page-Title` | | `metadata_icon` | `metadata.icon`: `{ url, type }` of the favicon | `X-Icon` | | `metadata_open_graph` | `metadata.open_graph`: `og:*` tags without the prefix | `X-Open-Graph` | | `metadata_fonts` | `metadata.fonts`: font families the page uses | `X-Fonts` | | `metadata_http_response_status_code` | `http_response.status_code` | `X-Http-Response` | | `metadata_http_response_headers` | `http_response.headers` | `X-Http-Response` | | `metadata_content` | `content`: `{ url, expires, format }` | `X-Content-Url`, `X-Content-Expires` | | `vision_prompt` + `openai_api_key` | `vision.completion` | `X-Vision` | Everything at once: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://github.com","response_type":"json","metadata_page_title":true,"metadata_icon":true,"metadata_open_graph":true,"metadata_fonts":true,"metadata_http_response_status_code":true}' \ --fail-with-body -o shot.json ``` ```json { "screenshot_url": "https://shotkit.net/cdn/files/…/….jpg", "metadata": { "page_title": "GitHub · Build and ship software on a single, collaborative platform", "icon": { "url": "https://github.githubassets.com/favicons/favicon.svg", "type": "image/svg+xml" }, "open_graph": { "title": "GitHub", "image": "https://github.githubassets.com/…", "type": "website" }, "fonts": ["Mona Sans", "-apple-system", "BlinkMacSystemFont"] }, "http_response": { "status_code": 200 } } ``` Header values are URL-encoded JSON. To read one: ```js const title = JSON.parse(decodeURIComponent(res.headers.get("x-page-title"))); ``` ## Errors Failures return JSON with a `4xx` or `5xx` status, whatever the response type. See [Errors](https://shotkit.net/docs/errors.md). --- # Caching > Store renders on a CDN and serve repeat requests for free, with control over lifetime and cache keys. ## Turn it on With `cache=true`, the result is stored and identical requests get the stored file instead of a new render: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","cache":true,"cache_ttl":86400}' \ --fail-with-body -o shot.jpg ``` - **Cache hits are free.** They don't count toward your monthly screenshots (they do count toward the [per-minute rate limit](https://shotkit.net/docs/limits.md)). - `GET` hits are redirected (`302`) to the file on the CDN, so `` tags load straight from it. `POST` hits return the file. - `cache_ttl` sets the lifetime, from 4 hours (`14400`, the default) to 30 days (`2592000`). - Caches are per workspace; other customers never get your renders. - [Async](https://shotkit.net/docs/async-and-webhooks.md) requests always render and don't use the cache. This makes [signed URLs](https://shotkit.net/docs/authentication.md#signed-urls) cheap to embed: the first view renders, and every later view of the same URL is a free redirect. ## What counts as identical The cache key is your workspace plus every option except these, which don't change the render: `cache`, `cache_ttl`, `response_type`, `async`, `webhook_url`, `webhook_sign`, `webhook_errors` and `external_identifier`. Option order doesn't matter. The site's content isn't part of the key: if the page changes, you keep getting the cached version until it expires. ### Force a fresh render Send a `Cache-Control: no-cache` request header. The page is rendered again and the cache entry is replaced: ```bash curl "https://shotkit.net/api/take?url=https://example.com&cache=true" \ -H "X-Access-Key: YOUR_ACCESS_KEY" -H "Cache-Control: no-cache" -o shot.jpg ``` ### Separate entries with `cache_key` `cache_key` is any string that becomes part of the key. Use it to roll a new version on your schedule, for example one render per day: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","cache":true,"cache_key":"2026-10-09"}' \ --fail-with-body -o shot.jpg ``` ## Response headers | Header | Value | | --- | --- | | `X-Cache` | `MISS` when rendered now, `HIT` when served from the cache. | | `X-Cache-Url` | The file's CDN URL, valid until the entry expires. | | `Cache-Control` | `private, max-age=`, so browsers keep their copy too. | With `response_type=json`, the body also has `cache_url`. ## Warm the cache Render ahead of time without downloading the file by combining `cache=true` with `response_type=empty`: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","cache":true,"response_type":"empty"}' \ --fail-with-body -o shot.jpg ``` --- # Async and webhooks > Queue renders in the background and receive the result as a signed POST to your webhook. ## Queue a render With `async=true`, the request returns `202 Accepted` right away and the render runs in the background. When it's done, the result is POSTed to `webhook_url`: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true,"async":true,"webhook_url":"https://your.app/hooks/shot","external_identifier":"page-42"}' \ --fail-with-body -o shot.jpg ``` ```json { "is_successful": true, "status": "accepted" } ``` Async requests always render; they don't read or write the [cache](https://shotkit.net/docs/caching.md). Use async for slow pages, long [recordings](https://shotkit.net/docs/video.md), and batches where you don't want to hold connections open. `timeout` can go up to 600 seconds with async (90 without). The monthly quota is checked when you queue the request. Your plan's per-minute rate limit applies to queued requests too. ## The webhook shotkit POSTs the same response a synchronous request would have returned: - With the default `response_type`, the body is the file and `Content-Type` its media type. - With `response_type=json`, the body is the [JSON](https://shotkit.net/docs/responses.md#json) with `screenshot_url` and metadata. This is the easiest to handle, and the only way to get metadata with async. Headers on the delivery: | Header | Value | | --- | --- | | `Content-Type` | The file's media type, or `application/json`. | | `X-Signature` | Hex HMAC-SHA256 of the raw body, keyed with your secret key. Omitted with `webhook_sign=false`. | | `X-External-Identifier` | Your `external_identifier`, if you set one. | Respond with any `2xx` within 15 seconds. Deliveries aren't retried, so store the body first and process it later. ### Errors By default, failed renders aren't delivered. With `webhook_errors=true`, the [error JSON](https://shotkit.net/docs/errors.md) is POSTed instead, signed the same way: ```json { "is_successful": false, "error_code": "timeout_error", "error_message": "Navigation timed out." } ``` Temporary failures are retried automatically before an error is sent: when all browsers are busy, every 15 seconds for up to an hour; after an infrastructure error, up to three times. Errors caused by the request or the page, such as an invalid selector or a `404`, fail right away. ## Verify the signature Compute the HMAC-SHA256 of the **raw** request body (before any JSON parsing) with your secret key and compare it with `X-Signature` in constant time: ```js tab="Node.js" import { createHmac, timingSafeEqual } from "node:crypto"; import express from "express"; const app = express(); app.post("/hooks/shot", express.raw({ type: "*/*", limit: "50mb" }), (req, res) => { const expected = createHmac("sha256", process.env.SHOTKIT_SECRET_KEY).update(req.body).digest("hex"); const given = req.get("x-signature") ?? ""; if (given.length !== expected.length || !timingSafeEqual(Buffer.from(given), Buffer.from(expected))) { return res.sendStatus(401); } // req.body is the file (or JSON), req.get("x-external-identifier") is your id. res.sendStatus(204); }); ``` ```ts tab="Next.js" import { createHmac, timingSafeEqual } from "node:crypto"; export async function POST(req: Request) { const body = Buffer.from(await req.arrayBuffer()); const expected = createHmac("sha256", process.env.SHOTKIT_SECRET_KEY!).update(body).digest("hex"); const given = req.headers.get("x-signature") ?? ""; if (given.length !== expected.length || !timingSafeEqual(Buffer.from(given), Buffer.from(expected))) { return new Response(null, { status: 401 }); } const id = req.headers.get("x-external-identifier"); // Store `body` for `id`… return new Response(null, { status: 204 }); } ``` ```python tab="Python" import hashlib, hmac, os from flask import Flask, request, abort app = Flask(__name__) @app.post("/hooks/shot") def shot(): body = request.get_data() expected = hmac.new(os.environ["SHOTKIT_SECRET_KEY"].encode(), body, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, request.headers.get("X-Signature", "")): abort(401) external_id = request.headers.get("X-External-Identifier") # Store `body` for `external_id`… return "", 204 ``` ```php tab="PHP" Give LLMs and agents eyes on the web with screenshots, Markdown and vision, and point them at these docs. ## Pages as Markdown `format=markdown` renders a page in a real browser and returns its content as Markdown: JavaScript-rendered content included, scripts and styles removed. It's a compact way to put a web page into an LLM's context. ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"markdown","block_cookie_banners":true,"selector":"main"}' \ --fail-with-body -o shot.md ``` `selector` narrows it to the part that matters and saves tokens. For pages behind a login, pass [cookies or an Authorization header](https://shotkit.net/docs/options/request.md). ## Screenshots for vision models Vision models read screenshots well. Keep them small: a 1280-wide viewport capture as JPG is plenty, and `image_width` scales it down further: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"jpg","image_quality":70,"image_width":1024,"block_cookie_banners":true,"block_chats":true}' \ --fail-with-body -o shot.jpg ``` To get the picture and the text in one render, add `metadata_content` with `response_type=json`; see [HTML and Markdown](https://shotkit.net/docs/html-and-markdown.md#a-screenshot-and-the-content-in-one-request). ## Ask a question about the page With your OpenAI key and a `vision_prompt`, the screenshot is sent to an OpenAI vision model and its answer comes back with the result: ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","openai_api_key":"sk-YOUR_OPENAI_KEY","vision_prompt":"Is there a cookie banner on this page? Answer yes or no.","vision_max_tokens":10}' \ --fail-with-body -o shot.json ``` ```json { "screenshot_url": "https://shotkit.net/cdn/files/…/….jpg", "vision": { "completion": "No" } } ``` Your key is used for this request only and never stored. OpenAI errors fail the request with `vision_error`. ## Give your agent these docs Every page of this documentation is plain Markdown for agents: - Add `.md` to any docs URL: [https://shotkit.net/docs/quickstart.md](https://shotkit.net/docs/quickstart.md). - Or request the normal URL with `Accept: text/markdown`. - [https://shotkit.net/llms.txt](https://shotkit.net/llms.txt) lists every page with a summary ([llms.txt convention](https://llmstxt.org)). - [https://shotkit.net/llms-full.txt](https://shotkit.net/llms-full.txt) is the whole documentation in one file. In the Markdown version, examples are cURL commands, so an agent can run them directly. The **Copy page** button at the top of each page copies the same Markdown, ready to paste into a chat. A system prompt line that works well: ```text To capture web pages, use the shotkit API. Its documentation is at https://shotkit.net/llms.txt. The access key is in the SHOTKIT_ACCESS_KEY environment variable. ``` --- # Options > All 107 options of the shotkit screenshot API: types, defaults, ranges and how to pass them. All 107 options, grouped as in the playground: [Essentials](https://shotkit.net/docs/options/essentials.md) · [Viewport](https://shotkit.net/docs/options/viewport.md) · [Image](https://shotkit.net/docs/options/image.md) · [Full page](https://shotkit.net/docs/options/full-page.md) · [Clip](https://shotkit.net/docs/options/clip.md) · [PDF](https://shotkit.net/docs/options/pdf.md) · [Animation](https://shotkit.net/docs/options/animation.md) · [Emulation](https://shotkit.net/docs/options/emulation.md) · [Customization](https://shotkit.net/docs/options/customization.md) · [Blocking](https://shotkit.net/docs/options/blocking.md) · [Wait](https://shotkit.net/docs/options/wait.md) · [Request](https://shotkit.net/docs/options/request.md) · [Geolocation](https://shotkit.net/docs/options/geolocation.md) · [Caching](https://shotkit.net/docs/options/caching.md) · [Metadata](https://shotkit.net/docs/options/metadata.md) · [Async & webhooks](https://shotkit.net/docs/options/async.md) · [Errors](https://shotkit.net/docs/options/errors.md) · [OpenAI vision](https://shotkit.net/docs/options/vision.md). ## Passing options Every option works in both request styles: - **POST** a JSON body: booleans and numbers as JSON values, lists as arrays (`"block_resources": ["font", "media"]`). - **GET** with a query string: `true`/`false` for booleans, and repeat a key for lists (`block_resources=font&block_resources=media`). Enum lists also accept commas (`wait_until=load,networkidle2`), and list options accept newlines. Unknown options are rejected with `invalid_parameter`, so typos don't silently do nothing. Empty values are ignored. ## All options | Option | Type | Default | Description | | --- | --- | --- | --- | | [`url`](https://shotkit.net/docs/options/essentials.md#url) | string | | URL of the site to screenshot. | | [`html`](https://shotkit.net/docs/options/essentials.md#html) | string | | HTML to render instead of a URL. | | [`markdown`](https://shotkit.net/docs/options/essentials.md#markdown) | string | | Markdown to render instead of a URL. | | [`format`](https://shotkit.net/docs/options/essentials.md#format) | enum | `jpg` | Response format. | | [`response_type`](https://shotkit.net/docs/options/essentials.md#response_type) | enum | `by_format` | Return the file, JSON metadata, or nothing. | | [`selector`](https://shotkit.net/docs/options/essentials.md#selector) | string | | CSS selector of the element to capture. | | [`selector_scroll_into_view`](https://shotkit.net/docs/options/essentials.md#selector_scroll_into_view) | boolean | `true` | Scroll to the element to trigger lazy content. | | [`capture_beyond_viewport`](https://shotkit.net/docs/options/essentials.md#capture_beyond_viewport) | boolean | | Capture parts outside the viewport (full page / selector). | | [`scroll_into_view`](https://shotkit.net/docs/options/essentials.md#scroll_into_view) | string | | Scroll this selector to the top before capture. | | [`scroll_into_view_adjust_top`](https://shotkit.net/docs/options/essentials.md#scroll_into_view_adjust_top) | number | `0` | Pixel offset after scroll_into_view. | | [`attachment_name`](https://shotkit.net/docs/options/essentials.md#attachment_name) | string | | Download filename (extension added). | | [`external_identifier`](https://shotkit.net/docs/options/essentials.md#external_identifier) | string | | Your ID, echoed in webhook headers. | | [`include_shadow_dom`](https://shotkit.net/docs/options/essentials.md#include_shadow_dom) | boolean | `false` | Include shadow roots in html/markdown output. | | [`viewport_device`](https://shotkit.net/docs/options/viewport.md#viewport_device) | enum | | Emulate a device preset (sets size, DPR, UA, touch). | | [`viewport_width`](https://shotkit.net/docs/options/viewport.md#viewport_width) | number | `1280` | Viewport width in px. | | [`viewport_height`](https://shotkit.net/docs/options/viewport.md#viewport_height) | number | `1024` | Viewport height in px. | | [`device_scale_factor`](https://shotkit.net/docs/options/viewport.md#device_scale_factor) | number | `1` | Device pixel ratio (1–5). Animated formats record at 1×. | | [`viewport_mobile`](https://shotkit.net/docs/options/viewport.md#viewport_mobile) | boolean | `false` | Respect the meta viewport tag. | | [`viewport_has_touch`](https://shotkit.net/docs/options/viewport.md#viewport_has_touch) | boolean | `false` | Enable touch events. | | [`viewport_landscape`](https://shotkit.net/docs/options/viewport.md#viewport_landscape) | boolean | `false` | Landscape orientation. | | [`image_quality`](https://shotkit.net/docs/options/image.md#image_quality) | number | `80` | Quality for lossy formats and video (0–100). | | [`image_width`](https://shotkit.net/docs/options/image.md#image_width) | number | | Resize output to fit this width (keeps ratio). | | [`image_height`](https://shotkit.net/docs/options/image.md#image_height) | number | | Resize output to fit this height (keeps ratio). | | [`omit_background`](https://shotkit.net/docs/options/image.md#omit_background) | boolean | `false` | Transparent background (PNG only). | | [`full_page`](https://shotkit.net/docs/options/full-page.md#full_page) | boolean | `false` | Capture the full scrollable page. | | [`full_page_scroll`](https://shotkit.net/docs/options/full-page.md#full_page_scroll) | boolean | | Scroll to bottom first to load lazy images (auto with full_page). | | [`full_page_scroll_delay`](https://shotkit.net/docs/options/full-page.md#full_page_scroll_delay) | number | `400` | Delay between scroll steps (ms). | | [`full_page_scroll_by`](https://shotkit.net/docs/options/full-page.md#full_page_scroll_by) | number | | Pixels per scroll step (default: viewport height). | | [`full_page_max_height`](https://shotkit.net/docs/options/full-page.md#full_page_max_height) | number | | Cap the full-page height (px). | | [`full_page_slices`](https://shotkit.net/docs/options/full-page.md#full_page_slices) | boolean | `false` | Split into vertical slices (URLs returned). | | [`full_page_slice_height`](https://shotkit.net/docs/options/full-page.md#full_page_slice_height) | number | `4000` | Max height per slice. | | [`full_page_slice_overlap_height`](https://shotkit.net/docs/options/full-page.md#full_page_slice_overlap_height) | number | `0` | Overlap between slices. | | [`clip_x`](https://shotkit.net/docs/options/clip.md#clip_x) | number | | Clip area X. | | [`clip_y`](https://shotkit.net/docs/options/clip.md#clip_y) | number | | Clip area Y. | | [`clip_width`](https://shotkit.net/docs/options/clip.md#clip_width) | number | | Clip area width. | | [`clip_height`](https://shotkit.net/docs/options/clip.md#clip_height) | number | | Clip area height. | | [`pdf_print_background`](https://shotkit.net/docs/options/pdf.md#pdf_print_background) | boolean | `false` | Print background graphics. | | [`pdf_fit_one_page`](https://shotkit.net/docs/options/pdf.md#pdf_fit_one_page) | boolean | `false` | Fit the whole page on one PDF page. | | [`pdf_landscape`](https://shotkit.net/docs/options/pdf.md#pdf_landscape) | boolean | `false` | Landscape orientation. | | [`pdf_paper_format`](https://shotkit.net/docs/options/pdf.md#pdf_paper_format) | enum | `letter` | Paper size. | | [`pdf_margin`](https://shotkit.net/docs/options/pdf.md#pdf_margin) | string | | Margin for all sides (e.g. 20px, 1cm). | | [`pdf_margin_top`](https://shotkit.net/docs/options/pdf.md#pdf_margin_top) | string | | Top margin override. | | [`pdf_margin_right`](https://shotkit.net/docs/options/pdf.md#pdf_margin_right) | string | | Right margin override. | | [`pdf_margin_bottom`](https://shotkit.net/docs/options/pdf.md#pdf_margin_bottom) | string | | Bottom margin override. | | [`pdf_margin_left`](https://shotkit.net/docs/options/pdf.md#pdf_margin_left) | string | | Left margin override. | | [`animation_duration`](https://shotkit.net/docs/options/animation.md#animation_duration) | number | `3` | Seconds to record. With format=webp, records an animated WebP. | | [`animation_fps`](https://shotkit.net/docs/options/animation.md#animation_fps) | number | | Frames per second (default: 10 for gif/webp, 30 for video). | | [`animation_scroll`](https://shotkit.net/docs/options/animation.md#animation_scroll) | boolean | `false` | Scroll smoothly from the current position to the bottom while recording. | | [`dark_mode`](https://shotkit.net/docs/options/emulation.md#dark_mode) | boolean | | Emulate prefers-color-scheme: dark. Otherwise pages render in light mode. | | [`reduce_motion`](https://shotkit.net/docs/options/emulation.md#reduce_motion) | boolean | `false` | Actively finish/pause animations and videos. | | [`reduced_motion`](https://shotkit.net/docs/options/emulation.md#reduced_motion) | boolean | | Emulate prefers-reduced-motion: reduce. | | [`media_type`](https://shotkit.net/docs/options/emulation.md#media_type) | enum | | CSS media type. | | [`hide_selectors`](https://shotkit.net/docs/options/customization.md#hide_selectors) | list | | Hide every element matching each selector. | | [`styles`](https://shotkit.net/docs/options/customization.md#styles) | string | | CSS injected before capture. | | [`scripts`](https://shotkit.net/docs/options/customization.md#scripts) | string | | JavaScript executed before capture. | | [`scripts_wait_until`](https://shotkit.net/docs/options/customization.md#scripts_wait_until) | enum list | | Wait for these events after scripts run. | | [`click`](https://shotkit.net/docs/options/customization.md#click) | string | | Click this selector before capture. | | [`hover`](https://shotkit.net/docs/options/customization.md#hover) | string | | Hover this selector before capture. | | [`error_on_click_selector_not_found`](https://shotkit.net/docs/options/customization.md#error_on_click_selector_not_found) | boolean | `true` | Fail if the click target is missing. | | [`error_on_hover_selector_not_found`](https://shotkit.net/docs/options/customization.md#error_on_hover_selector_not_found) | boolean | `true` | Fail if the hover target is missing. | | [`block_cookie_banners`](https://shotkit.net/docs/options/blocking.md#block_cookie_banners) | boolean | `false` | Hide cookie / GDPR banners. | | [`block_banners_by_heuristics`](https://shotkit.net/docs/options/blocking.md#block_banners_by_heuristics) | boolean | `false` | Aggressive heuristic banner removal. | | [`block_chats`](https://shotkit.net/docs/options/blocking.md#block_chats) | boolean | `false` | Hide chat widgets (Intercom, Crisp, Drift…). | | [`block_ads`](https://shotkit.net/docs/options/blocking.md#block_ads) | boolean | `false` | Block ad networks. | | [`block_trackers`](https://shotkit.net/docs/options/blocking.md#block_trackers) | boolean | `false` | Block analytics / trackers. | | [`block_requests`](https://shotkit.net/docs/options/blocking.md#block_requests) | list | | Block requests matching wildcard patterns. | | [`block_resources`](https://shotkit.net/docs/options/blocking.md#block_resources) | enum list | | Block resource types. | | [`wait_until`](https://shotkit.net/docs/options/wait.md#wait_until) | enum list | `load` | Navigation events to wait for. | | [`delay`](https://shotkit.net/docs/options/wait.md#delay) | number | `0` | Extra seconds to wait before capture. | | [`timeout`](https://shotkit.net/docs/options/wait.md#timeout) | number | `60` | Total request timeout (s). Up to 90, or 600 with async (render time only). | | [`navigation_timeout`](https://shotkit.net/docs/options/wait.md#navigation_timeout) | number | `30` | Navigation timeout (s). | | [`wait_for_selector`](https://shotkit.net/docs/options/wait.md#wait_for_selector) | string | | Wait for this selector to appear. | | [`wait_for_selector_algorithm`](https://shotkit.net/docs/options/wait.md#wait_for_selector_algorithm) | enum | `at_least_one` | How comma-separated selectors are matched. | | [`user_agent`](https://shotkit.net/docs/options/request.md#user_agent) | string | | Custom User-Agent. | | [`authorization`](https://shotkit.net/docs/options/request.md#authorization) | string | | Authorization header for the target. | | [`headers`](https://shotkit.net/docs/options/request.md#headers) | list | | Extra headers, one per line. | | [`cookies`](https://shotkit.net/docs/options/request.md#cookies) | list | | Cookies, one per line. | | [`time_zone`](https://shotkit.net/docs/options/request.md#time_zone) | enum | | Browser time zone. | | [`proxy`](https://shotkit.net/docs/options/request.md#proxy) | string | | Your HTTP proxy (http://user:pass@host:port). | | [`bypass_csp`](https://shotkit.net/docs/options/request.md#bypass_csp) | boolean | `false` | Bypass Content-Security-Policy. | | [`geolocation_latitude`](https://shotkit.net/docs/options/geolocation.md#geolocation_latitude) | number | | Latitude. | | [`geolocation_longitude`](https://shotkit.net/docs/options/geolocation.md#geolocation_longitude) | number | | Longitude. | | [`geolocation_accuracy`](https://shotkit.net/docs/options/geolocation.md#geolocation_accuracy) | number | | Accuracy in meters. | | [`cache`](https://shotkit.net/docs/options/caching.md#cache) | boolean | `false` | Cache the result and return a CDN URL. | | [`cache_ttl`](https://shotkit.net/docs/options/caching.md#cache_ttl) | number | `14400` | Cache lifetime in seconds. | | [`cache_key`](https://shotkit.net/docs/options/caching.md#cache_key) | string | | Distinguish otherwise identical cached renders. | | [`metadata_image_size`](https://shotkit.net/docs/options/metadata.md#metadata_image_size) | boolean | `false` | Return image width/height. | | [`metadata_page_title`](https://shotkit.net/docs/options/metadata.md#metadata_page_title) | boolean | `false` | Return the page title. | | [`metadata_icon`](https://shotkit.net/docs/options/metadata.md#metadata_icon) | boolean | `false` | Return the favicon URL. | | [`metadata_open_graph`](https://shotkit.net/docs/options/metadata.md#metadata_open_graph) | boolean | `false` | Return Open Graph tags. | | [`metadata_fonts`](https://shotkit.net/docs/options/metadata.md#metadata_fonts) | boolean | `false` | Return fonts used by the page. | | [`metadata_content`](https://shotkit.net/docs/options/metadata.md#metadata_content) | boolean | `false` | Also store page content and return its URL. | | [`metadata_content_format`](https://shotkit.net/docs/options/metadata.md#metadata_content_format) | enum | `html` | Content format. | | [`metadata_http_response_status_code`](https://shotkit.net/docs/options/metadata.md#metadata_http_response_status_code) | boolean | `false` | Return the target's HTTP status. | | [`metadata_http_response_headers`](https://shotkit.net/docs/options/metadata.md#metadata_http_response_headers) | boolean | `false` | Return the target's HTTP headers. | | [`async`](https://shotkit.net/docs/options/async.md#async) | boolean | `false` | Return 202 immediately and render in the background. | | [`webhook_url`](https://shotkit.net/docs/options/async.md#webhook_url) | string | | POST the result here when done. | | [`webhook_sign`](https://shotkit.net/docs/options/async.md#webhook_sign) | boolean | `true` | Sign webhook body with your secret key. | | [`webhook_errors`](https://shotkit.net/docs/options/async.md#webhook_errors) | boolean | `false` | Also send errors to the webhook. | | [`ignore_host_errors`](https://shotkit.net/docs/options/errors.md#ignore_host_errors) | boolean | `false` | Capture even if the site returns 4xx/5xx. | | [`error_on_selector_not_found`](https://shotkit.net/docs/options/errors.md#error_on_selector_not_found) | boolean | `false` | Fail if selector / scroll_into_view is missing. | | [`fail_if_request_failed`](https://shotkit.net/docs/options/errors.md#fail_if_request_failed) | list | | Fail if a matching sub-request fails. | | [`fail_if_content_missing`](https://shotkit.net/docs/options/errors.md#fail_if_content_missing) | list | | Fail if this text is missing (case-insensitive). | | [`fail_if_content_contains`](https://shotkit.net/docs/options/errors.md#fail_if_content_contains) | list | | Fail if this text is present (case-insensitive). | | [`openai_api_key`](https://shotkit.net/docs/options/vision.md#openai_api_key) | string | | Your OpenAI key (never stored). | | [`vision_prompt`](https://shotkit.net/docs/options/vision.md#vision_prompt) | string | | Prompt sent with the screenshot. | | [`vision_max_tokens`](https://shotkit.net/docs/options/vision.md#vision_max_tokens) | number | | Max completion tokens. | --- # Essentials options > Reference for the essentials options of the shotkit screenshot API, with an example request for each. The source to render (exactly one of `url`, `html` or `markdown`), the output format, and what to capture. ### `url` URL of the site to screenshot. Must be `http://` or `https://`. Redirects are followed. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://github.com"}' \ --fail-with-body -o shot.jpg ``` ### `html` HTML to render instead of a URL. Rendered as a page with no origin. Relative URLs don't resolve, so use absolute URLs for images, stylesheets and fonts. Cookies need an explicit `Domain`. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"html":"
Hello, world
","viewport_width":1200,"viewport_height":630,"format":"png"}' \ --fail-with-body -o shot.png ``` ### `markdown` Markdown to render instead of a URL. Converted to HTML and rendered with a clean, readable stylesheet. Useful for social cards, changelogs and reports. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"markdown":"# Release notes\n\n- Faster renders\n- Video output\n- Cookie banner blocking","format":"png"}' \ --fail-with-body -o shot.png ``` ### `format` Response format. `jpeg` is an alias for `jpg`. `gif`, `mp4` and `webm` are recorded over time (see [Animation](https://shotkit.net/docs/options/animation.md)); `webp` is a still image unless an animation option is set. `html` and `markdown` return the rendered page's content as text instead of an image. Type: `enum` · Default: `jpg` Values: `jpg`, `jpeg`, `png`, `webp`, `avif`, `gif`, `tiff`, `pdf`, `html`, `markdown`, `mp4`, `webm` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"webp"}' \ --fail-with-body -o shot.webp ``` ### `response_type` Return the file, JSON metadata, or nothing. `by_format` returns the file itself. `json` uploads the file and returns its URL with any metadata. `empty` returns `200` with no body, which is useful with `async` or to warm the cache. Type: `enum` · Default: `by_format` Values: `by_format`, `json`, `empty` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json"}' \ --fail-with-body -o shot.json ``` ### `selector` CSS selector of the element to capture. Captures only the first matching element. With `html`/`markdown` output, returns that element's markup. If nothing matches, the full viewport is captured unless `error_on_selector_not_found=true`. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","selector":"main"}' \ --fail-with-body -o shot.jpg ``` ### `selector_scroll_into_view` Scroll to the element to trigger lazy content. Type: `boolean` · Default: `true` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","selector":"footer","selector_scroll_into_view":false}' \ --fail-with-body -o shot.jpg ``` ### `capture_beyond_viewport` Capture parts outside the viewport (full page / selector). Type: `boolean` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","selector":"#pricing","capture_beyond_viewport":true}' \ --fail-with-body -o shot.jpg ``` ### `scroll_into_view` Scroll this selector to the top before capture. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","scroll_into_view":"#pricing"}' \ --fail-with-body -o shot.jpg ``` ### `scroll_into_view_adjust_top` Pixel offset after scroll_into_view. Type: `number` · Default: `0` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","scroll_into_view":"#pricing","scroll_into_view_adjust_top":-80}' \ --fail-with-body -o shot.jpg ``` ### `attachment_name` Download filename (extension added). Sets `Content-Disposition: attachment`, so browsers download the file instead of displaying it. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","attachment_name":"homepage"}' \ --fail-with-body -o shot.jpg ``` ### `external_identifier` Your ID, echoed in webhook headers. Sent back as the `X-External-Identifier` header on webhook deliveries, so you can match results to your records. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","async":true,"webhook_url":"https://your.app/hooks/shot","external_identifier":"order-1234"}' \ --fail-with-body -o shot.jpg ``` ### `include_shadow_dom` Include shadow roots in html/markdown output. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"html","include_shadow_dom":true}' \ --fail-with-body -o shot.html ``` --- # Viewport options > Reference for the viewport options of the shotkit screenshot API, with an example request for each. The browser window the page is rendered in. Pick a device preset or set the size yourself; explicit options override the preset. ### `viewport_device` Emulate a device preset (sets size, DPR, UA, touch). Sets width, height, device pixel ratio, user agent, touch and mobile mode in one go. All ids are listed below. Type: `enum`
All 153 device ids `blackberry_playbook`, `blackberry_playbook_landscape`, `blackberry_z30`, `blackberry_z30_landscape`, `galaxy_note_3`, `galaxy_note_3_landscape`, `galaxy_note_ii`, `galaxy_note_ii_landscape`, `galaxy_s5`, `galaxy_s5_landscape`, `galaxy_s8`, `galaxy_s8_landscape`, `galaxy_s9+`, `galaxy_s9+_landscape`, `galaxy_s_iii`, `galaxy_s_iii_landscape`, `galaxy_tab_s4`, `galaxy_tab_s4_landscape`, `ipad`, `ipad_(gen_6)`, `ipad_(gen_6)_landscape`, `ipad_(gen_7)`, `ipad_(gen_7)_landscape`, `ipad_landscape`, `ipad_mini`, `ipad_mini_landscape`, `ipad_pro`, `ipad_pro_11`, `ipad_pro_11_landscape`, `ipad_pro_landscape`, `iphone_11`, `iphone_11_landscape`, `iphone_11_pro`, `iphone_11_pro_landscape`, `iphone_11_pro_max`, `iphone_11_pro_max_landscape`, `iphone_12`, `iphone_12_landscape`, `iphone_12_mini`, `iphone_12_mini_landscape`, `iphone_12_pro`, `iphone_12_pro_landscape`, `iphone_12_pro_max`, `iphone_12_pro_max_landscape`, `iphone_13`, `iphone_13_landscape`, `iphone_13_mini`, `iphone_13_mini_landscape`, `iphone_13_pro`, `iphone_13_pro_landscape`, `iphone_13_pro_max`, `iphone_13_pro_max_landscape`, `iphone_14`, `iphone_14_landscape`, `iphone_14_plus`, `iphone_14_plus_landscape`, `iphone_14_pro`, `iphone_14_pro_landscape`, `iphone_14_pro_max`, `iphone_14_pro_max_landscape`, `iphone_15`, `iphone_15_landscape`, `iphone_15_plus`, `iphone_15_plus_landscape`, `iphone_15_pro`, `iphone_15_pro_landscape`, `iphone_15_pro_max`, `iphone_15_pro_max_landscape`, `iphone_16`, `iphone_16_landscape`, `iphone_16_plus`, `iphone_16_plus_landscape`, `iphone_16_pro`, `iphone_16_pro_landscape`, `iphone_16_pro_max`, `iphone_16_pro_max_landscape`, `iphone_16e`, `iphone_16e_landscape`, `iphone_17`, `iphone_17_landscape`, `iphone_17_pro`, `iphone_17_pro_landscape`, `iphone_17_pro_max`, `iphone_17_pro_max_landscape`, `iphone_17e`, `iphone_17e_landscape`, `iphone_4`, `iphone_4_landscape`, `iphone_5`, `iphone_5_landscape`, `iphone_6`, `iphone_6_landscape`, `iphone_6_plus`, `iphone_6_plus_landscape`, `iphone_7`, `iphone_7_landscape`, `iphone_7_plus`, `iphone_7_plus_landscape`, `iphone_8`, `iphone_8_landscape`, `iphone_8_plus`, `iphone_8_plus_landscape`, `iphone_air`, `iphone_air_landscape`, `iphone_se`, `iphone_se_(3rd_gen)`, `iphone_se_(3rd_gen)_landscape`, `iphone_se_landscape`, `iphone_x`, `iphone_x_landscape`, `iphone_xr`, `iphone_xr_landscape`, `jiophone_2`, `jiophone_2_landscape`, `kindle_fire_hdx`, `kindle_fire_hdx_landscape`, `lg_optimus_l70`, `lg_optimus_l70_landscape`, `microsoft_lumia_550`, `microsoft_lumia_950`, `microsoft_lumia_950_landscape`, `moto_g4`, `moto_g4_landscape`, `nexus_10`, `nexus_10_landscape`, `nexus_4`, `nexus_4_landscape`, `nexus_5`, `nexus_5_landscape`, `nexus_5x`, `nexus_5x_landscape`, `nexus_6`, `nexus_6_landscape`, `nexus_6p`, `nexus_6p_landscape`, `nexus_7`, `nexus_7_landscape`, `nokia_lumia_520`, `nokia_lumia_520_landscape`, `nokia_n9`, `nokia_n9_landscape`, `pixel_2`, `pixel_2_landscape`, `pixel_2_xl`, `pixel_2_xl_landscape`, `pixel_3`, `pixel_3_landscape`, `pixel_4`, `pixel_4_landscape`, `pixel_4a_(5g)`, `pixel_4a_(5g)_landscape`, `pixel_5`, `pixel_5_landscape`
```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","viewport_device":"iphone_15_pro"}' \ --fail-with-body -o shot.jpg ``` ### `viewport_width` Viewport width in px. Type: `number` · Default: `1280` · Range: `1`–`7680` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","viewport_width":1920}' \ --fail-with-body -o shot.jpg ``` ### `viewport_height` Viewport height in px. Type: `number` · Default: `1024` · Range: `1`–`7680` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","viewport_height":1080}' \ --fail-with-body -o shot.jpg ``` ### `device_scale_factor` Device pixel ratio (1–5). Animated formats record at 1×. A 1280×1024 viewport at `2` produces a 2560×2048 image. Type: `number` · Default: `1` · Range: `1`–`5` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","device_scale_factor":2}' \ --fail-with-body -o shot.jpg ``` ### `viewport_mobile` Respect the meta viewport tag. Makes the page honour ``, like a phone browser. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","viewport_width":390,"viewport_height":844,"viewport_mobile":true}' \ --fail-with-body -o shot.jpg ``` ### `viewport_has_touch` Enable touch events. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","viewport_width":390,"viewport_height":844,"viewport_has_touch":true}' \ --fail-with-body -o shot.jpg ``` ### `viewport_landscape` Landscape orientation. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","viewport_device":"ipad_pro_11","viewport_landscape":true}' \ --fail-with-body -o shot.jpg ``` --- # Image options > Reference for the image options of the shotkit screenshot API, with an example request for each. Encoding of image output. Pages are captured losslessly once, then resized and encoded, so these options never change the layout. ### `image_quality` Quality for lossy formats and video (0–100). Applies to `jpg`, `webp`, `avif`, `tiff`, and to video bitrate. For `png`, a value below 100 enables palette quantisation for much smaller files. Type: `number` · Default: `80` · Range: `0`–`100` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"webp","image_quality":60}' \ --fail-with-body -o shot.webp ``` ### `image_width` Resize output to fit this width (keeps ratio). Images are only scaled down, never up. With both `image_width` and `image_height`, the result fits inside the box. Type: `number` · Range: `1`–`16000` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","image_width":640}' \ --fail-with-body -o shot.jpg ``` ### `image_height` Resize output to fit this height (keeps ratio). Type: `number` · Range: `1`–`16000` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","image_height":400}' \ --fail-with-body -o shot.jpg ``` ### `omit_background` Transparent background (PNG only). Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"html":"
Passing
","selector":"div","format":"png","omit_background":true}' \ --fail-with-body -o shot.png ``` --- # Full page options > Reference for the full page options of the shotkit screenshot API, with an example request for each. Capture the whole scrollable page instead of the viewport. The page is scrolled first so lazy-loaded images and sections render. ### `full_page` Capture the full scrollable page. Not available for recorded formats (`gif`, `mp4`, `webm`, animated `webp`); use `animation_scroll` to record the whole page instead. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true}' \ --fail-with-body -o shot.jpg ``` ### `full_page_scroll` Scroll to bottom first to load lazy images (auto with full_page). Type: `boolean` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true,"full_page_scroll":false}' \ --fail-with-body -o shot.jpg ``` ### `full_page_scroll_delay` Delay between scroll steps (ms). Type: `number` · Default: `400` · Range: `0`–`5000` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true,"full_page_scroll_delay":800}' \ --fail-with-body -o shot.jpg ``` ### `full_page_scroll_by` Pixels per scroll step (default: viewport height). Type: `number` · Min: `50` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true,"full_page_scroll_by":500}' \ --fail-with-body -o shot.jpg ``` ### `full_page_max_height` Cap the full-page height (px). Type: `number` · Min: `1` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true,"full_page_max_height":6000}' \ --fail-with-body -o shot.jpg ``` ### `full_page_slices` Split into vertical slices (URLs returned). Returns the slices as URLs: in the JSON body with `response_type=json`, otherwise in the `X-Full-Page-Slices-Url` header (a JSON file listing them). The full image is still returned too. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true,"full_page_slices":true,"response_type":"json"}' \ --fail-with-body -o shot.json ``` ### `full_page_slice_height` Max height per slice. Type: `number` · Default: `4000` · Range: `1`–`16000` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true,"full_page_slices":true,"full_page_slice_height":2000,"response_type":"json"}' \ --fail-with-body -o shot.json ``` ### `full_page_slice_overlap_height` Overlap between slices. Type: `number` · Default: `0` · Min: `0` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","full_page":true,"full_page_slices":true,"full_page_slice_height":2000,"full_page_slice_overlap_height":100,"response_type":"json"}' \ --fail-with-body -o shot.json ``` --- # Clip options > Reference for the clip options of the shotkit screenshot API, with an example request for each. Capture a rectangle of the page, in CSS pixels from the top-left of the document. Set `clip_width` and `clip_height`; `clip_x` and `clip_y` default to 0. A `selector` takes precedence. ### `clip_x` Clip area X. Type: `number` · Min: `0` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","clip_x":100,"clip_y":200,"clip_width":600,"clip_height":400}' \ --fail-with-body -o shot.jpg ``` ### `clip_y` Clip area Y. Type: `number` · Min: `0` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","clip_x":100,"clip_y":200,"clip_width":600,"clip_height":400}' \ --fail-with-body -o shot.jpg ``` ### `clip_width` Clip area width. Type: `number` · Min: `1` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","clip_width":600,"clip_height":400}' \ --fail-with-body -o shot.jpg ``` ### `clip_height` Clip area height. Type: `number` · Min: `1` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","clip_width":600,"clip_height":400}' \ --fail-with-body -o shot.jpg ``` --- # PDF options > Reference for the pdf options of the shotkit screenshot API, with an example request for each. Options for `format=pdf`. Margins accept any CSS length (`20px`, `1cm`, `0.5in`). ### `pdf_print_background` Print background graphics. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","pdf_print_background":true}' \ --fail-with-body -o shot.pdf ``` ### `pdf_fit_one_page` Fit the whole page on one PDF page. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","pdf_fit_one_page":true,"pdf_print_background":true}' \ --fail-with-body -o shot.pdf ``` ### `pdf_landscape` Landscape orientation. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","pdf_landscape":true}' \ --fail-with-body -o shot.pdf ``` ### `pdf_paper_format` Paper size. Type: `enum` · Default: `letter` Values: `a0`, `a1`, `a2`, `a3`, `a4`, `a5`, `a6`, `legal`, `letter`, `tabloid` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","pdf_paper_format":"a4"}' \ --fail-with-body -o shot.pdf ``` ### `pdf_margin` Margin for all sides (e.g. 20px, 1cm). Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","pdf_margin":"1cm"}' \ --fail-with-body -o shot.pdf ``` ### `pdf_margin_top` Top margin override. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","pdf_margin":"1cm","pdf_margin_top":"2cm"}' \ --fail-with-body -o shot.pdf ``` ### `pdf_margin_right` Right margin override. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","pdf_margin":"1cm","pdf_margin_right":"2cm"}' \ --fail-with-body -o shot.pdf ``` ### `pdf_margin_bottom` Bottom margin override. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","pdf_margin":"1cm","pdf_margin_bottom":"2cm"}' \ --fail-with-body -o shot.pdf ``` ### `pdf_margin_left` Left margin override. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"pdf","pdf_margin":"1cm","pdf_margin_left":"2cm"}' \ --fail-with-body -o shot.pdf ``` --- # Animation options > Reference for the animation options of the shotkit screenshot API, with an example request for each. Options for the recorded formats: `gif`, `mp4` and `webm`. Setting any of them with `format=webp` records an animated WebP instead of a still image. The page is recorded in real time for `animation_duration` seconds. ### `animation_duration` Seconds to record. With format=webp, records an animated WebP. Type: `number` · Default: `3` · Range: `0.5`–`20` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"mp4","animation_duration":5}' \ --fail-with-body -o shot.mp4 ``` ### `animation_fps` Frames per second (default: 10 for gif/webp, 30 for video). Type: `number` · Range: `1`–`30` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"gif","animation_fps":15}' \ --fail-with-body -o shot.gif ``` ### `animation_scroll` Scroll smoothly from the current position to the bottom while recording. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"mp4","animation_scroll":true,"animation_duration":8}' \ --fail-with-body -o shot.mp4 ``` --- # Emulation options > Reference for the emulation options of the shotkit screenshot API, with an example request for each. Media features and types the page sees, for screenshots in dark mode, without animations, or as printed. ### `dark_mode` Emulate prefers-color-scheme: dark. Otherwise pages render in light mode. Type: `boolean` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","dark_mode":true}' \ --fail-with-body -o shot.jpg ``` ### `reduce_motion` Actively finish/pause animations and videos. Finishes CSS animations and transitions, pauses videos and emulates `prefers-reduced-motion`. Fixes half-faded hero sections. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","reduce_motion":true}' \ --fail-with-body -o shot.jpg ``` ### `reduced_motion` Emulate prefers-reduced-motion: reduce. Only sets the media feature; pages decide what to do with it. See `reduce_motion` for the active version. Type: `boolean` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","reduced_motion":true}' \ --fail-with-body -o shot.jpg ``` ### `media_type` CSS media type. Type: `enum` Values: `screen`, `print` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","media_type":"print"}' \ --fail-with-body -o shot.jpg ``` --- # Customization options > Reference for the customization options of the shotkit screenshot API, with an example request for each. Change the page before capture: hide elements, inject CSS or JavaScript, click and hover. They run after the page has loaded, in the order listed here. ### `hide_selectors` Hide every element matching each selector. Elements get `display: none !important`. Type: `list` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","hide_selectors":[".newsletter-popup","#promo-bar"]}' \ --fail-with-body -o shot.jpg ``` ### `styles` CSS injected before capture. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","styles":"header { position: static !important }"}' \ --fail-with-body -o shot.jpg ``` ### `scripts` JavaScript executed before capture. Runs in the page after it loads, before clicks and the capture. If it throws, the request fails with `script_triggers_error`. If it navigates, set `scripts_wait_until`. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","scripts":"document.querySelector('\''details'\'')?.setAttribute('\''open'\'', '\'''\'')"}' \ --fail-with-body -o shot.jpg ``` ### `scripts_wait_until` Wait for these events after scripts run. Type: `enum list` Values: `load`, `domcontentloaded`, `networkidle0`, `networkidle2` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","scripts":"document.querySelector('\''a.next'\'').click()","scripts_wait_until":["networkidle2"]}' \ --fail-with-body -o shot.jpg ``` ### `click` Click this selector before capture. Waits for the element to become visible first. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","click":"button.load-more"}' \ --fail-with-body -o shot.jpg ``` ### `hover` Hover this selector before capture. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","hover":"nav .products"}' \ --fail-with-body -o shot.jpg ``` ### `error_on_click_selector_not_found` Fail if the click target is missing. Type: `boolean` · Default: `true` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","click":"#close-promo","error_on_click_selector_not_found":false}' \ --fail-with-body -o shot.jpg ``` ### `error_on_hover_selector_not_found` Fail if the hover target is missing. Type: `boolean` · Default: `true` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","hover":"nav .products","error_on_hover_selector_not_found":false}' \ --fail-with-body -o shot.jpg ``` --- # Blocking options > Reference for the blocking options of the shotkit screenshot API, with an example request for each. Remove clutter. Host lists block requests before they load; cookie banner and chat blocking also hide known elements with CSS. ### `block_cookie_banners` Hide cookie / GDPR banners. Blocks known consent-manager scripts (OneTrust, Cookiebot, Didomi, …), hides their elements, and restores page scrolling they may have locked. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","block_cookie_banners":true}' \ --fail-with-body -o shot.jpg ``` ### `block_banners_by_heuristics` Aggressive heuristic banner removal. Also removes fixed or sticky overlays that look like banners or modals. Aggressive: check the result on your pages. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","block_cookie_banners":true,"block_banners_by_heuristics":true}' \ --fail-with-body -o shot.jpg ``` ### `block_chats` Hide chat widgets (Intercom, Crisp, Drift…). Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","block_chats":true}' \ --fail-with-body -o shot.jpg ``` ### `block_ads` Block ad networks. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","block_ads":true}' \ --fail-with-body -o shot.jpg ``` ### `block_trackers` Block analytics / trackers. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","block_trackers":true}' \ --fail-with-body -o shot.jpg ``` ### `block_requests` Block requests matching wildcard patterns. `*` matches any characters. Matched against the full request URL. The page itself is never blocked. Type: `list` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","block_requests":["*.doubleclick.net/*","*/analytics.js"]}' \ --fail-with-body -o shot.jpg ``` ### `block_resources` Block resource types. Type: `enum list` Values: `document`, `stylesheet`, `image`, `media`, `font`, `script`, `texttrack`, `xhr`, `fetch`, `eventsource`, `websocket`, `manifest`, `other` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","block_resources":["media","font"]}' \ --fail-with-body -o shot.jpg ``` --- # Wait options > Reference for the wait options of the shotkit screenshot API, with an example request for each. When to take the shot. By default, after the `load` event, once the network is briefly idle, the page has stopped changing, and fonts and visible images have loaded. ### `wait_until` Navigation events to wait for. `networkidle0`: no requests for 500 ms. `networkidle2`: at most 2 open requests for 500 ms. Several values wait for all of them. Type: `enum list` · Default: `load` Values: `load`, `domcontentloaded`, `networkidle0`, `networkidle2` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","wait_until":["networkidle0"]}' \ --fail-with-body -o shot.jpg ``` ### `delay` Extra seconds to wait before capture. Type: `number` · Default: `0` · Range: `0`–`30` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","delay":2}' \ --fail-with-body -o shot.jpg ``` ### `timeout` Total request timeout (s). Up to 90, or 600 with async (render time only). Covers the whole request, including waiting for a free browser. Above 90 seconds requires `async=true`, where it covers only the render. Type: `number` · Default: `60` · Range: `5`–`600` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","timeout":30}' \ --fail-with-body -o shot.jpg ``` ### `navigation_timeout` Navigation timeout (s). Type: `number` · Default: `30` · Range: `1`–`30` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","navigation_timeout":15}' \ --fail-with-body -o shot.jpg ``` ### `wait_for_selector` Wait for this selector to appear. Comma-separated selectors wait for any one of them, or all of them with `wait_for_selector_algorithm=at_least_by_count`. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","wait_for_selector":".chart canvas"}' \ --fail-with-body -o shot.jpg ``` ### `wait_for_selector_algorithm` How comma-separated selectors are matched. Type: `enum` · Default: `at_least_one` Values: `at_least_one`, `at_least_by_count` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","wait_for_selector":".chart, .table","wait_for_selector_algorithm":"at_least_by_count"}' \ --fail-with-body -o shot.jpg ``` --- # Request options > Reference for the request options of the shotkit screenshot API, with an example request for each. How the browser talks to the target site: identity, credentials, locale and network. ### `user_agent` Custom User-Agent. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","user_agent":"Mozilla/5.0 (compatible; MyPreviewBot/1.0)"}' \ --fail-with-body -o shot.jpg ``` ### `authorization` Authorization header for the target. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://app.example.com/dashboard","authorization":"Bearer YOUR_APP_TOKEN"}' \ --fail-with-body -o shot.jpg ``` ### `headers` Extra headers, one per line. `Name: value`, one per line in GET (or repeat the key), or an array in JSON. Type: `list` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","headers":["Accept-Language: de-DE","X-Preview: true"]}' \ --fail-with-body -o shot.jpg ``` ### `cookies` Cookies, one per line. Same syntax as `Set-Cookie`: `name=value; Domain=…; Path=…; Secure; HttpOnly`. `Domain` defaults to the `url`'s host. Type: `list` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://app.example.com/dashboard","cookies":["session=abc123; Secure; HttpOnly"]}' \ --fail-with-body -o shot.jpg ``` ### `time_zone` Browser time zone. Type: `enum` Values: `America/Chicago`, `America/Denver`, `America/Los_Angeles`, `America/Mexico_City`, `America/New_York`, `America/Santiago`, `America/Toronto`, `America/Vancouver`, `Asia/Kuala_Lumpur`, `Asia/Shanghai`, `Asia/Tashkent`, `Asia/Tokyo`, `Europe/Berlin`, `Europe/Bucharest`, `Europe/Kyiv`, `Europe/Lisbon`, `Europe/London`, `Europe/Madrid`, `Europe/Paris`, `Pacific/Auckland`, `UTC` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","time_zone":"Asia/Tokyo"}' \ --fail-with-body -o shot.jpg ``` ### `proxy` Your HTTP proxy (http://user:pass@host:port). Credentials in the URL are used for proxy authentication. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","proxy":"http://user:pass@proxy.example.com:8080"}' \ --fail-with-body -o shot.jpg ``` ### `bypass_csp` Bypass Content-Security-Policy. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","bypass_csp":true,"scripts":"document.body.append(Object.assign(document.createElement('\''script'\''), { src: '\''https://cdn.example.com/widget.js'\'' }))"}' \ --fail-with-body -o shot.jpg ``` --- # Geolocation options > Reference for the geolocation options of the shotkit screenshot API, with an example request for each. Grant the page the geolocation permission and report these coordinates. Latitude and longitude must be set together. Pages served over plain `http://` can't read the location (browser rule). ### `geolocation_latitude` Latitude. Type: `number` · Range: `-90`–`90` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://www.openstreetmap.org","geolocation_latitude":48.8566,"geolocation_longitude":2.3522}' \ --fail-with-body -o shot.jpg ``` ### `geolocation_longitude` Longitude. Type: `number` · Range: `-180`–`180` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://www.openstreetmap.org","geolocation_latitude":48.8566,"geolocation_longitude":2.3522}' \ --fail-with-body -o shot.jpg ``` ### `geolocation_accuracy` Accuracy in meters. Type: `number` · Min: `0` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://www.openstreetmap.org","geolocation_latitude":48.8566,"geolocation_longitude":2.3522,"geolocation_accuracy":50}' \ --fail-with-body -o shot.jpg ``` --- # Caching options > Reference for the caching options of the shotkit screenshot API, with an example request for each. Store the render and reuse it for identical requests. Cache hits are free and don't count toward your monthly screenshots. ### `cache` Cache the result and return a CDN URL. Identical requests (same options, same workspace) return the stored file until it expires. GET requests are redirected (`302`) to the file's CDN URL; POST requests get the bytes. Send `Cache-Control: no-cache` to force a fresh render that also refreshes the entry. See [Caching](https://shotkit.net/docs/caching.md). Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","cache":true}' \ --fail-with-body -o shot.jpg ``` ### `cache_ttl` Cache lifetime in seconds. Between 4 hours (`14400`) and 30 days (`2592000`). Also how long uploaded files and URLs from `response_type=json` stay available. Type: `number` · Default: `14400` · Range: `14400`–`2592000` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","cache":true,"cache_ttl":86400}' \ --fail-with-body -o shot.jpg ``` ### `cache_key` Distinguish otherwise identical cached renders. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","cache":true,"cache_key":"2026-10-09"}' \ --fail-with-body -o shot.jpg ``` --- # Metadata options > Reference for the metadata options of the shotkit screenshot API, with an example request for each. Extra information about the page. With `response_type=json` it is returned in the JSON body; otherwise each field is sent as an `X-…` response header (URL-encoded JSON). ### `metadata_image_size` Return image width/height. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","metadata_image_size":true}' \ --fail-with-body -o shot.json ``` ### `metadata_page_title` Return the page title. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","metadata_page_title":true}' \ --fail-with-body -o shot.json ``` ### `metadata_icon` Return the favicon URL. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","metadata_icon":true}' \ --fail-with-body -o shot.json ``` ### `metadata_open_graph` Return Open Graph tags. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","metadata_open_graph":true}' \ --fail-with-body -o shot.json ``` ### `metadata_fonts` Return fonts used by the page. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","metadata_fonts":true}' \ --fail-with-body -o shot.json ``` ### `metadata_content` Also store page content and return its URL. Uploads the page's HTML (or Markdown) next to the screenshot and returns its URL, so you get a screenshot and the text in one render. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","metadata_content":true}' \ --fail-with-body -o shot.json ``` ### `metadata_content_format` Content format. Type: `enum` · Default: `html` Values: `html`, `markdown` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","metadata_content":true,"metadata_content_format":"markdown"}' \ --fail-with-body -o shot.json ``` ### `metadata_http_response_status_code` Return the target's HTTP status. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","metadata_http_response_status_code":true}' \ --fail-with-body -o shot.json ``` ### `metadata_http_response_headers` Return the target's HTTP headers. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","metadata_http_response_headers":true}' \ --fail-with-body -o shot.json ``` --- # Async & webhooks options > Reference for the async & webhooks options of the shotkit screenshot API, with an example request for each. Return `202 Accepted` immediately and render in the background. The result is POSTed to `webhook_url`. See [Async & webhooks](https://shotkit.net/docs/async-and-webhooks.md). ### `async` Return 202 immediately and render in the background. Requires `webhook_url`. Allows `timeout` up to 600 seconds. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","async":true,"webhook_url":"https://your.app/hooks/shot"}' \ --fail-with-body -o shot.jpg ``` ### `webhook_url` POST the result here when done. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","async":true,"webhook_url":"https://your.app/hooks/shot"}' \ --fail-with-body -o shot.jpg ``` ### `webhook_sign` Sign webhook body with your secret key. Adds `X-Signature`: the hex HMAC-SHA256 of the raw body, keyed with your secret key. See [verifying webhooks](https://shotkit.net/docs/async-and-webhooks.md#verify-the-signature). Type: `boolean` · Default: `true` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","async":true,"webhook_url":"https://your.app/hooks/shot","webhook_sign":false}' \ --fail-with-body -o shot.jpg ``` ### `webhook_errors` Also send errors to the webhook. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","async":true,"webhook_url":"https://your.app/hooks/shot","webhook_errors":true}' \ --fail-with-body -o shot.jpg ``` --- # Errors options > Reference for the errors options of the shotkit screenshot API, with an example request for each. Fail the request instead of returning a screenshot of a broken page. Failed requests don't count toward your monthly screenshots. ### `ignore_host_errors` Capture even if the site returns 4xx/5xx. By default, a 4xx or 5xx from the site fails the request with `host_returned_error`. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/missing-page","ignore_host_errors":true}' \ --fail-with-body -o shot.jpg ``` ### `error_on_selector_not_found` Fail if selector / scroll_into_view is missing. Type: `boolean` · Default: `false` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","selector":"#chart","error_on_selector_not_found":true}' \ --fail-with-body -o shot.jpg ``` ### `fail_if_request_failed` Fail if a matching sub-request fails. Wildcard patterns matched against sub-request URLs. A request fails when it errors or returns 4xx/5xx. Type: `list` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","fail_if_request_failed":["*api.example.com*"]}' \ --fail-with-body -o shot.jpg ``` ### `fail_if_content_missing` Fail if this text is missing (case-insensitive). Matched against the page's visible text. Catches login walls and empty states. Type: `list` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","fail_if_content_missing":["Add to cart"]}' \ --fail-with-body -o shot.jpg ``` ### `fail_if_content_contains` Fail if this text is present (case-insensitive). Type: `list` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","fail_if_content_contains":["Access denied","captcha"]}' \ --fail-with-body -o shot.jpg ``` --- # OpenAI vision options > Reference for the openai vision options of the shotkit screenshot API, with an example request for each. Send the screenshot to an OpenAI vision model with your prompt and return its answer. Your OpenAI key is used for this request only and never stored. ### `openai_api_key` Your OpenAI key (never stored). Required for vision, together with `vision_prompt`. The model is `gpt-4o-mini` unless configured otherwise. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","openai_api_key":"sk-YOUR_OPENAI_KEY","vision_prompt":"Describe this page in one sentence."}' \ --fail-with-body -o shot.json ``` ### `vision_prompt` Prompt sent with the screenshot. The answer is returned as `vision.completion` with `response_type=json`, otherwise in the `X-Vision` header. Type: `string` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","openai_api_key":"sk-YOUR_OPENAI_KEY","vision_prompt":"Is there a cookie banner on this page? Answer yes or no."}' \ --fail-with-body -o shot.json ``` ### `vision_max_tokens` Max completion tokens. Type: `number` · Range: `1`–`4096` ```bash curl -X POST "https://shotkit.net/api/take" \ -H "X-Access-Key: YOUR_ACCESS_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","response_type":"json","openai_api_key":"sk-YOUR_OPENAI_KEY","vision_prompt":"Summarize this page.","vision_max_tokens":200}' \ --fail-with-body -o shot.json ``` --- # Errors > Every error code the API returns, what causes it, and what to do about it. ## Error format Errors are JSON, with an HTTP status of 400 or above: ```json { "is_successful": false, "error_code": "too_many_requests", "error_message": "Rate limit of 10 requests per minute exceeded.", "retry_after": 12, "limit": 10 } ``` Branch on `error_code`, which is stable; `error_message` is for people and may change. Some errors add fields, listed below. When a request can be retried later, the response also has a `Retry-After` header in seconds. Failed requests don't count toward your monthly screenshots. ## Request errors | Code | Status | Cause | | --- | --- | --- | | `invalid_request` | 400 | The POST body isn't a JSON object. | | `invalid_parameter` | 400 | An unknown option, a value of the wrong type or out of range, or options that don't go together. The message says which. | | `invalid_url` | 400 | `url` isn't a valid `http(s)` URL. | | `access_key_invalid` | 401 | No access key, or not a valid one. See [Authentication](https://shotkit.net/docs/authentication.md). | | `email_not_verified` | 403 | On the Hobby plan, nobody in the workspace has verified their email address yet. | | `signature_is_not_valid` | 403 | The signature doesn't match the query string (GET) or the body (POST). See [signed requests](https://shotkit.net/docs/authentication.md#signed-requests). | | `signature_is_required` | 403 | The workspace [requires signed requests](https://shotkit.net/docs/authentication.md#require-signed-requests) and this one has no signature. | ## Limits | Code | Status | Extra fields | Cause | | --- | --- | --- | --- | | `too_many_requests` | 429 | `retry_after`, `limit`, `upgrade_url` | Over your plan's requests per minute. Wait `retry_after` seconds. | | `screenshots_limit_reached` | 429 | `limit`, `resets_at`, `retry_after`, `upgrade_url` | This month's screenshots are used up. Resets on the 1st (UTC). | `upgrade_url` is left out on the largest plan. See [Limits](https://shotkit.net/docs/limits.md). ## Page errors | Code | Status | Extra fields | Cause | | --- | --- | --- | --- | | `host_returned_error` | 400 | `returned_status_code` | The page returned 4xx or 5xx. Set `ignore_host_errors=true` to capture it anyway. | | `name_not_resolved` | 400 | | The domain doesn't exist. | | `network_error` | 400 | | The page couldn't be loaded (connection refused, TLS error, …). The message has Chrome's error. | | `timeout_error` | 504 | | The page or the whole request took longer than `navigation_timeout` or `timeout`. | | `selector_not_found` | 400 | | `wait_for_selector`, `click` or `hover` didn't match a visible element, or `selector`/`scroll_into_view` didn't with `error_on_selector_not_found=true`. | | `script_triggers_error` | 400 | | Your `scripts` threw. The message has the error. | ## Checks These come from the `fail_if_*` options, which turn a bad page into an error instead of a screenshot. See [Clean screenshots](https://shotkit.net/docs/clean-screenshots.md#fail-instead-of-capturing-a-broken-page). | Code | Status | Extra fields | Cause | | --- | --- | --- | --- | | `content_missing_specified_string` | 400 | `missing_string` | A `fail_if_content_missing` text isn't on the page. | | `content_contains_specified_string` | 400 | `matched_string` | A `fail_if_content_contains` text is on the page. | | `matched_failed_request` | 400 | `failed_request_url` | A request matching `fail_if_request_failed` failed or returned 4xx/5xx. | | `vision_error` | 400 | | OpenAI rejected the vision request. The message has OpenAI's error. | ## Server errors | Code | Status | Extra fields | Cause | | --- | --- | --- | --- | | `server_busy` | 503 | `retry_after` | All browsers are busy. Retry after a few seconds. | | `queue_unavailable` | 503 | `retry_after` | An `async` request couldn't be queued. Retry shortly. | | `video_not_supported` | 501 | | Video output isn't available on this server. | | `internal_application_error` | 500 | | Something went wrong on our side. Retry; if it keeps happening, contact us. | ## Retrying Retry `429`, `503` and `504` responses, waiting at least `Retry-After` seconds. Don't retry other `4xx` errors without changing the request; they fail the same way every time. ```js async function take(params, attempts = 3) { for (let i = 1; ; i++) { const res = await fetch("https://shotkit.net/api/take", { method: "POST", headers: { "X-Access-Key": process.env.SHOTKIT_ACCESS_KEY, "Content-Type": "application/json" }, body: JSON.stringify(params), }); if (res.ok) return Buffer.from(await res.arrayBuffer()); const err = await res.json(); const retryable = [429, 503, 504].includes(res.status) && err.error_code !== "screenshots_limit_reached"; if (!retryable || i >= attempts) throw new Error(`${err.error_code}: ${err.error_message}`); await new Promise((r) => setTimeout(r, Number(res.headers.get("retry-after") ?? 5) * 1000)); } } ``` --- # Limits and plans > Monthly screenshots, requests per minute, timeouts and file retention for each plan. ## Plans | Plan | Price | Screenshots / month | Requests / minute | | --- | --- | --- | --- | | Hobby | Free | 100 | 10 | | Starter | $9/month | 2,000 | 30 | | Growth | $29/month | 10,000 | 60 | Every plan has every option. Paying yearly saves 20%. Change plans on the [billing page](https://shotkit.net/billing); changes apply within a minute. ## Monthly screenshots The monthly limit counts **successful, uncached renders**. Not counted: - [cache hits](https://shotkit.net/docs/caching.md), - failed requests (any error), - requests rejected by a limit. The count resets on the 1st of each month at 00:00 UTC. Over the limit, requests fail with `screenshots_limit_reached` (`429`), which says when it resets. Your current usage is on the [usage page](https://shotkit.net/usage). ## Requests per minute Every authenticated request counts toward the per-minute limit, including cache hits and failed ones. The window is a calendar minute. Over the limit, requests fail with `too_many_requests` (`429`) and `Retry-After` says when the next window starts. All keys and members of a workspace share its limits. ## Timeouts | Limit | Default | Maximum | | --- | --- | --- | | `timeout` (whole request) | 60 s | 90 s, or 600 s with [async](https://shotkit.net/docs/async-and-webhooks.md) | | `navigation_timeout` (page load) | 30 s | 30 s | | `delay` | 0 s | 30 s | | `animation_duration` | 3 s | 20 s | ## Sizes | Limit | Value | | --- | --- | | Viewport | 1–7680 px each way, device scale factor up to 5 | | Output resize (`image_width`/`image_height`) | up to 16,000 px | | Full-page scrolling | stops at 50,000 px | | Slices | up to 16,000 px each | ## File retention Files from `response_type=json`, cached renders, slices and stored content are deleted after `cache_ttl`: 4 hours by default, up to 30 days. Download anything you need to keep.