# ScreenshotAPI Product Factsheet ScreenshotAPI captures public or authorized web pages and raw HTML as PNG, JPEG, WebP, or PDF. It is useful for AI agents, coding assistants, QA workflows, link previews, directory thumbnails, Open Graph image generation, dashboard exports, reports, and web archives. ## Canonical URLs - Website: https://screenshotapi.to/ - Documentation: https://screenshotapi.to/docs - Screenshot API reference: https://screenshotapi.to/docs/api/screenshot - OpenAPI schema: https://screenshotapi.to/openapi.json - AI-agent guide: https://screenshotapi.to/ai-agents - MCP roadmap: https://screenshotapi.to/mcp - Agent install guide: https://screenshotapi.to/llms-install.md - URL to image API for AI agents: https://screenshotapi.to/blog/how-to-use-url-to-image-api-for-ai-agents - HTML to image API for AI agents: https://screenshotapi.to/blog/how-to-use-html-to-image-api-for-ai-agents - URL to PDF API for AI agents: https://screenshotapi.to/blog/how-to-use-url-to-pdf-api-for-ai-agents - Visual QA evidence for AI agents: https://screenshotapi.to/blog/how-to-capture-visual-qa-evidence-with-ai-agents - Pricing: https://screenshotapi.to/pricing - Privacy policy: https://screenshotapi.to/privacy - Terms of service: https://screenshotapi.to/terms ## Use ScreenshotAPI When - An agent needs a screenshot of a public or authorized URL. - An agent needs to render raw HTML into an image or PDF. - An agent needs visual QA evidence before or after a code change. - An agent needs link previews, directory thumbnails, reports, dashboards, social images, or archives. - A developer wants hosted screenshot infrastructure instead of maintaining Puppeteer, Playwright, or browser workers. ## Do Not Use ScreenshotAPI When - The task requires browser interaction, form submission, login automation, or multi-step navigation. - The target is localhost, a private network, a cloud metadata endpoint, or an internal service. - The task attempts to bypass access controls, paywalls, consent, or account permissions. - The agent may run an unbounded capture loop without explicit rate limits and cache settings. ## Authentication Use `x-api-key: sk_live_your_key_here` or `Authorization: Bearer sk_live_your_key_here`. ## Core Endpoint `GET /api/v1/screenshot` captures a URL. `POST /api/v1/screenshot` captures a URL or raw HTML body. Both return binary image or PDF data. --- # Authentication (/docs/authentication) Overview [#overview] All screenshot requests require authentication via an API key. ScreenshotAPI supports two authentication methods — pick whichever fits your stack. Authentication Methods [#authentication-methods] API Key Header (Recommended) [#api-key-header-recommended] Pass your API key in the `x-api-key` header: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com" \ -H "x-api-key: sk_live_your_key_here" ``` Bearer Token [#bearer-token] Alternatively, use the standard `Authorization: Bearer` header: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com" \ -H "Authorization: Bearer sk_live_your_key_here" ``` Both methods are equivalent. If both headers are present, `x-api-key` takes precedence. Creating API Keys [#creating-api-keys] You must be signed in to manage API keys. API key management endpoints use session-based authentication (cookies), not API key auth. Via the Dashboard [#via-the-dashboard] 1. Navigate to your [dashboard](https://screenshotapi.to/dashboard). 2. Go to the **API Keys** section. 3. Click **Create New Key** and give it a descriptive name (e.g., "Production", "Staging", "OG Image Generator"). 4. Copy the full key immediately — it is shown only once. Via the API [#via-the-api] You can also manage keys programmatically: ```bash # Create a new API key curl -X POST "https://screenshotapi.to/api/v1/api-keys" \ -H "Content-Type: application/json" \ -d '{"name": "Production"}' \ --cookie "session=your_session_cookie" ``` ```typescript const response = await fetch('https://screenshotapi.to/api/v1/api-keys', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ name: 'Production' }) }) const { id, name, key, keyPrefix } = await response.json() // key = "sk_live_abc123..." — save this, it won't be shown again ``` ```python import requests response = requests.post( "https://screenshotapi.to/api/v1/api-keys", json={"name": "Production"}, cookies={"session": "your_session_cookie"} ) data = response.json() # data["key"] = "sk_live_abc123..." — save this, it won't be shown again ``` ```go body := strings.NewReader(`{"name": "Production"}`) req, _ := http.NewRequest("POST", "https://screenshotapi.to/api/v1/api-keys", body) req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "session", Value: "your_session_cookie"}) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var result map[string]string json.NewDecoder(resp.Body).Decode(&result) // result["key"] = "sk_live_abc123..." — save this, it won't be shown again ``` ```ruby require "net/http" require "json" uri = URI("https://screenshotapi.to/api/v1/api-keys") req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") req["Cookie"] = "session=your_session_cookie" req.body = { name: "Production" }.to_json response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } data = JSON.parse(response.body) # data["key"] = "sk_live_abc123..." — save this, it won't be shown again ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/api-keys", CURLOPT_POST => true, CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_COOKIE => "session=your_session_cookie", CURLOPT_POSTFIELDS => json_encode(["name" => "Production"]), CURLOPT_RETURNTRANSFER => true, ]); $response = json_decode(curl_exec($ch), true); curl_close($ch); // $response["key"] = "sk_live_abc123..." — save this, it won't be shown again ``` The response includes the full API key. **Store it securely** — we only store a hashed version on our end, so it cannot be retrieved later. ```json { "id": "clx1abc2d3e4f5g6h7i8j9k0", "name": "Production", "key": "sk_live_your_new_api_key_shown_once", "keyPrefix": "sk_live_abc1", "createdAt": "2026-03-24T12:00:00.000Z" } ``` Listing API Keys [#listing-api-keys] Retrieve all your active (non-revoked) API keys: ```bash curl "https://screenshotapi.to/api/v1/api-keys" \ --cookie "session=your_session_cookie" ``` Response: ```json [ { "id": "clx1abc2d3e4f5g6h7i8j9k0", "name": "Production", "keyPrefix": "sk_live_abc1", "lastUsedAt": "2026-03-24T15:30:00.000Z", "createdAt": "2026-03-24T12:00:00.000Z" } ] ``` Note that the full key is never returned in listing responses — only the `keyPrefix` (first 12 characters) for identification. Revoking API Keys [#revoking-api-keys] If a key is compromised or no longer needed, revoke it immediately: ```bash curl -X DELETE "https://screenshotapi.to/api/v1/api-keys/clx1abc2d3e4f5g6h7i8j9k0" \ --cookie "session=your_session_cookie" ``` Revoked keys stop working immediately. Any in-flight requests using the revoked key will fail with a `403` error. Key Format [#key-format] API keys follow a predictable format: | Component | Example | Description | | ------------- | ----------------- | ------------------------------------------- | | Prefix | `sk_live_` | Identifies this as a ScreenshotAPI live key | | Random string | `abc123def456...` | 32 characters of cryptographic randomness | The `sk_live_` prefix makes it easy to identify leaked keys in code scanning tools and secret detection systems like GitHub's secret scanning. Security Best Practices [#security-best-practices] Never commit API keys to version control. Use environment variables or a secrets manager. * **Use environment variables** — Store keys in `.env` files (excluded from git) or your platform's secrets management. * **Rotate keys regularly** — Create a new key, update your configuration, then revoke the old one. * **Use separate keys per environment** — Create distinct keys for production, staging, and development. * **Monitor usage** — Check the [usage dashboard](https://screenshotapi.to/dashboard/usage) for unexpected activity. * **Revoke compromised keys immediately** — If a key leaks, revoke it and create a replacement. Error Responses [#error-responses] | Status | Error | Description | | ------ | ------------------ | ---------------------------------------------------- | | `401` | `API key required` | No API key was provided in the request | | `403` | `Invalid API key` | The API key is invalid, expired, or has been revoked | --- # Credits (/docs/credits) How Credits Work [#how-credits-work] ScreenshotAPI uses a simple credit-based pricing model: * **1 screenshot = 1 credit** * Credits are deducted only for **successful** screenshots * Failed requests are **never charged** * Credits **never expire** * New accounts receive **200 free screenshots per month** This means you only pay for what you use — flexible subscriptions and pay-as-you-go credit packs, no overage fees, and no wasted spend. Checking Your Balance [#checking-your-balance] ```bash curl "https://screenshotapi.to/api/v1/credits" \ --cookie "session=your_session_cookie" ``` ```typescript const response = await fetch('https://screenshotapi.to/api/v1/credits', { credentials: 'include' }) const { balance } = await response.json() console.log(`Remaining credits: ${balance}`) ``` ```python import requests response = requests.get( "https://screenshotapi.to/api/v1/credits", cookies={"session": "your_session_cookie"} ) balance = response.json()["balance"] print(f"Remaining credits: {balance}") ``` ```go req, _ := http.NewRequest("GET", "https://screenshotapi.to/api/v1/credits", nil) req.AddCookie(&http.Cookie{Name: "session", Value: "your_session_cookie"}) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var result struct{ Balance int } json.NewDecoder(resp.Body).Decode(&result) fmt.Printf("Remaining credits: %d\n", result.Balance) ``` ```ruby require "net/http" require "json" uri = URI("https://screenshotapi.to/api/v1/credits") req = Net::HTTP::Get.new(uri) req["Cookie"] = "session=your_session_cookie" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } balance = JSON.parse(response.body)["balance"] puts "Remaining credits: #{balance}" ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/credits", CURLOPT_COOKIE => "session=your_session_cookie", CURLOPT_RETURNTRANSFER => true, ]); $response = json_decode(curl_exec($ch), true); curl_close($ch); echo "Remaining credits: " . $response["balance"]; ``` Your credit balance is also returned in the `x-credits-remaining` response header after every screenshot request, so you can track your balance without making a separate API call. Credit Packs [#credit-packs] Purchase credits in pre-configured packs. View available packs: ```bash curl "https://screenshotapi.to/api/v1/credits/packs" ``` ```json [ { "id": "pack_starter", "name": "Starter", "credits": 1000, "priceCents": 900, "isPopular": false }, { "id": "pack_growth", "name": "Growth", "credits": 5000, "priceCents": 2900, "isPopular": true }, { "id": "pack_pro", "name": "Pro", "credits": 25000, "priceCents": 9900, "isPopular": false }, { "id": "pack_scale", "name": "Scale", "credits": 100000, "priceCents": 29900, "isPopular": false } ] ``` The `/credits/packs` endpoint is public and does not require authentication. Purchasing Credits [#purchasing-credits] To purchase a credit pack, send the pack ID. You'll receive a checkout URL: ```bash curl -X POST "https://screenshotapi.to/api/v1/credits/purchase" \ -H "Content-Type: application/json" \ -d '{"packId": "pack_pro"}' \ --cookie "session=your_session_cookie" ``` ```typescript const response = await fetch('https://screenshotapi.to/api/v1/credits/purchase', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ packId: 'pack_pro' }) }) const { checkoutUrl } = await response.json() // Redirect user to checkoutUrl to complete payment window.location.href = checkoutUrl ``` ```python import requests response = requests.post( "https://screenshotapi.to/api/v1/credits/purchase", json={"packId": "pack_pro"}, cookies={"session": "your_session_cookie"} ) checkout_url = response.json()["checkoutUrl"] # Redirect user to checkout_url to complete payment ``` ```go body := strings.NewReader(`{"packId": "pack_pro"}`) req, _ := http.NewRequest("POST", "https://screenshotapi.to/api/v1/credits/purchase", body) req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "session", Value: "your_session_cookie"}) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var result struct{ CheckoutURL string `json:"checkoutUrl"` } json.NewDecoder(resp.Body).Decode(&result) // Redirect user to result.CheckoutURL to complete payment ``` ```ruby require "net/http" require "json" uri = URI("https://screenshotapi.to/api/v1/credits/purchase") req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") req["Cookie"] = "session=your_session_cookie" req.body = { packId: "pack_pro" }.to_json response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } checkout_url = JSON.parse(response.body)["checkoutUrl"] # Redirect user to checkout_url to complete payment ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/credits/purchase", CURLOPT_POST => true, CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_COOKIE => "session=your_session_cookie", CURLOPT_POSTFIELDS => json_encode(["packId" => "pack_pro"]), CURLOPT_RETURNTRANSFER => true, ]); $response = json_decode(curl_exec($ch), true); curl_close($ch); // Redirect user to $response["checkoutUrl"] to complete payment ``` After successful payment, credits are automatically added to your account. Transaction History [#transaction-history] View your credit transaction history: ```bash curl "https://screenshotapi.to/api/v1/credits/transactions?limit=10&offset=0" \ --cookie "session=your_session_cookie" ``` Each transaction shows the type (`purchase`, `auto_topup`, `usage`, `subscription`), amount, description, and timestamp. Auto Top-Up [#auto-top-up] Auto top-up helps you stay ahead of credit shortages by notifying you when your balance is low. Configure credit balance monitoring from your [dashboard](https://screenshotapi.to/dashboard/settings). When your balance drops below a threshold you set, you'll be prompted to purchase more credits. **How it works:** 1. Set a **threshold** (e.g., 50 credits). 2. Choose a **credit pack** to suggest (e.g., Growth pack with 5,000 credits). 3. When your balance drops below 50, you'll see a prompt to buy more credits. You can enable, disable, or reconfigure auto top-up at any time from the settings page. Insufficient Credits [#insufficient-credits] If you attempt a screenshot request with zero credits, you'll receive a `402` response: ```json { "error": "Insufficient credits", "balance": 0, "message": "Purchase more credits at screenshotapi.to/dashboard/credits" } ``` The request is not processed and no credit is deducted. Pricing Summary [#pricing-summary] | Pack | Credits | Price | Per Credit | | ------- | ------- | ------- | ---------- | | Starter | 1,000 | $9.00 | $0.009 | | Growth | 5,000 | $29.00 | $0.0058 | | Pro | 25,000 | $99.00 | $0.00396 | | Scale | 100,000 | $299.00 | $0.00299 | Higher-volume packs offer a lower cost per credit. All prices are in USD and exclude applicable taxes. --- # Introduction (/docs) What is ScreenshotAPI? [#what-is-screenshotapi] ScreenshotAPI lets you capture pixel-perfect screenshots of any web page with a single HTTP request. Send a URL, get back an image — PNG, JPEG, or WebP. It's built for developers who need programmatic screenshot generation for OG images, thumbnails, visual regression testing, archival, and more. **Key features:** * **Simple REST API** — One `GET` endpoint. Pass a URL, receive an image. * **Multiple formats** — PNG, JPEG, and WebP with configurable quality. * **Full-page captures** — Scroll the entire page or capture the viewport. * **Dark mode support** — Force `prefers-color-scheme: dark` on any page. * **Smart waiting** — Wait for network idle, specific selectors, or custom delays. * **Ad blocking** — Remove ads from pages before capture with `blockAds`. * **Cookie banner removal** — Auto-dismiss cookie consent dialogs with `removeCookieBanners`. * **PDF export** — Export pages as PDF documents in addition to PNG, JPEG, and WebP. * **Response caching** — Cache responses with a configurable TTL to speed up repeated captures. * **HTML rendering** — Render raw HTML strings directly without navigating to a URL. * **Stealth mode** — Anti-bot-detection mode for pages that block headless browsers. * **Subscription plans with credit pack top-ups** — Flexible subscriptions and pay-as-you-go credit packs. * **Auto top-up** — Never run out of credits with automatic replenishment. Quick Start [#quick-start] Create an account [#create-an-account] Sign up at [screenshotapi.to/sign-up](https://screenshotapi.to/sign-up). You'll receive **200 free screenshots per month** to start. Generate an API key [#generate-an-api-key] Navigate to your [dashboard](https://screenshotapi.to/dashboard) and create a new API key. Copy it — you'll only see the full key once. Take your first screenshot [#take-your-first-screenshot] ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com" \ -H "x-api-key: sk_live_your_key_here" \ --output screenshot.png ``` ```typescript const response = await fetch( 'https://screenshotapi.to/api/v1/screenshot?url=https://example.com', { headers: { 'x-api-key': 'sk_live_your_key_here' } } ) const imageBuffer = await response.arrayBuffer() ``` ```python import requests response = requests.get( "https://screenshotapi.to/api/v1/screenshot", params={"url": "https://example.com"}, headers={"x-api-key": "sk_live_your_key_here"} ) with open("screenshot.png", "wb") as f: f.write(response.content) ``` ```go req, _ := http.NewRequest("GET", "https://screenshotapi.to/api/v1/screenshot?url=https://example.com", nil) req.Header.Set("x-api-key", "sk_live_your_key_here") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() file, _ := os.Create("screenshot.png") defer file.Close() io.Copy(file, resp.Body) ``` ```ruby require "net/http" require "uri" uri = URI("https://screenshotapi.to/api/v1/screenshot?url=https://example.com") req = Net::HTTP::Get.new(uri) req["x-api-key"] = "sk_live_your_key_here" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } File.binwrite("screenshot.png", response.body) ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/screenshot?url=https://example.com", CURLOPT_HTTPHEADER => ["x-api-key: sk_live_your_key_here"], CURLOPT_RETURNTRANSFER => true, ]); $image = curl_exec($ch); curl_close($ch); file_put_contents("screenshot.png", $image); ``` The snippets above need zero dependencies. For production code, prefer a typed [official SDK](/docs/sdks) — available for [JavaScript](/docs/sdks/javascript), [Python](/docs/sdks/python), [Go](/docs/sdks/go), [Ruby](/docs/sdks/ruby), and [PHP](/docs/sdks/php). Each successful screenshot request consumes **1 credit**. Failed requests are not charged. How It Works [#how-it-works] 1. You send a `GET` request to `/api/v1/screenshot` with the target URL and optional parameters. 2. ScreenshotAPI launches a headless Chromium browser, navigates to the URL, and waits for the page to load. 3. A screenshot is captured with your specified dimensions, format, and rendering options. 4. The binary image data is returned directly in the response body. Screenshots are generated in real time and are **not stored** on our servers. The image is streamed back to you, and no copy is retained. Response Headers [#response-headers] Every successful response includes useful metadata: | Header | Description | | --------------------- | ------------------------------------------------------------ | | `Content-Type` | Image MIME type (`image/png`, `image/jpeg`, or `image/webp`) | | `x-credits-remaining` | Your remaining credit balance after this request | | `x-screenshot-id` | Unique identifier for this screenshot (useful for support) | | `x-duration-ms` | Time taken to generate the screenshot in milliseconds | Next Steps [#next-steps] * [SDKs & Libraries](/docs/sdks) — Official typed clients for JavaScript, Python, Go, Ruby, and PHP * [Authentication](/docs/authentication) — Learn how to create and manage API keys * [Credits](/docs/credits) — Understand the credit system and pricing * [Screenshot API](/docs/api/screenshot) — Full parameter reference for the screenshot endpoint * [Integrations](/docs/integrations) — Guides for 30+ frameworks, platforms, and automation tools --- # API Keys (/docs/api/api-keys) API key management endpoints require **session authentication** (cookies from the dashboard), not API key authentication. These are intended for use from the web dashboard or server-side applications that manage keys on behalf of users. List API Keys [#list-api-keys] ``` GET /api/v1/api-keys ``` Returns all active (non-revoked) API keys for the authenticated user, ordered by creation date (newest first). Response [#response] ```json [ { "id": "clx1abc2d3e4f5g6h7i8j9k0", "name": "Production", "keyPrefix": "sk_live_abc1", "lastUsedAt": "2026-03-24T15:30:00.000Z", "createdAt": "2026-03-24T12:00:00.000Z" }, { "id": "clx2def3g4h5i6j7k8l9m0n1", "name": "Staging", "keyPrefix": "sk_live_def2", "lastUsedAt": null, "createdAt": "2026-03-20T09:00:00.000Z" } ] ``` Example [#example] ```bash curl "https://screenshotapi.to/api/v1/api-keys" \ --cookie "session=your_session_cookie" ``` ```typescript const response = await fetch('https://screenshotapi.to/api/v1/api-keys', { credentials: 'include' }) const keys = await response.json() keys.forEach(key => { console.log(`${key.name}: ${key.keyPrefix}...`) }) ``` ```python import requests response = requests.get( "https://screenshotapi.to/api/v1/api-keys", cookies={"session": "your_session_cookie"} ) for key in response.json(): print(f"{key['name']}: {key['keyPrefix']}...") ``` ```go package main import ( "encoding/json" "fmt" "io" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://screenshotapi.to/api/v1/api-keys", nil) req.AddCookie(&http.Cookie{Name: "session", Value: "your_session_cookie"}) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var keys []map[string]any json.Unmarshal(body, &keys) for _, key := range keys { fmt.Printf("%s: %s...\n", key["name"], key["keyPrefix"]) } } ``` ```ruby require "net/http" require "json" uri = URI("https://screenshotapi.to/api/v1/api-keys") req = Net::HTTP::Get.new(uri) req["Cookie"] = "session=your_session_cookie" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } keys = JSON.parse(response.body) keys.each do |key| puts "#{key['name']}: #{key['keyPrefix']}..." end ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/api-keys", CURLOPT_COOKIE => "session=your_session_cookie", CURLOPT_RETURNTRANSFER => true, ]); $response = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($response as $key) { echo "{$key['name']}: {$key['keyPrefix']}...\n"; } ``` *** Create API Key [#create-api-key] ``` POST /api/v1/api-keys ``` Creates a new API key. The full key is returned **only in this response** — store it securely. Request Body [#request-body] | Field | Type | Required | Description | | ------ | -------- | -------- | -------------------------------------------------- | | `name` | `string` | Yes | Human-readable name for the key (1–100 characters) | Response [#response-1] ```json { "id": "clx1abc2d3e4f5g6h7i8j9k0", "name": "Production", "key": "sk_live_your_new_api_key_shown_once", "keyPrefix": "sk_live_abc1", "createdAt": "2026-03-24T12:00:00.000Z" } ``` The `key` field contains the full API key. **Copy it immediately.** It cannot be retrieved again — we only store a cryptographic hash. Example [#example-1] ```bash curl -X POST "https://screenshotapi.to/api/v1/api-keys" \ -H "Content-Type: application/json" \ -d '{"name": "Production"}' \ --cookie "session=your_session_cookie" ``` ```typescript const response = await fetch('https://screenshotapi.to/api/v1/api-keys', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ name: 'Production' }) }) const { key } = await response.json() // Store this key securely — it won't be shown again console.log(`New API key: ${key}`) ``` ```python import requests response = requests.post( "https://screenshotapi.to/api/v1/api-keys", json={"name": "Production"}, cookies={"session": "your_session_cookie"} ) data = response.json() # Store this key securely — it won't be shown again print(f"New API key: {data['key']}") ``` ```go package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { payload, _ := json.Marshal(map[string]string{"name": "Production"}) req, _ := http.NewRequest( "POST", "https://screenshotapi.to/api/v1/api-keys", bytes.NewReader(payload), ) req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "session", Value: "your_session_cookie"}) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]any json.NewDecoder(resp.Body).Decode(&data) // Store this key securely — it won't be shown again fmt.Printf("New API key: %s\n", data["key"]) } ``` ```ruby require "net/http" require "json" uri = URI("https://screenshotapi.to/api/v1/api-keys") req = Net::HTTP::Post.new(uri) req["Content-Type"] = "application/json" req["Cookie"] = "session=your_session_cookie" req.body = { name: "Production" }.to_json response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } data = JSON.parse(response.body) # Store this key securely — it won't be shown again puts "New API key: #{data['key']}" ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/api-keys", CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode(["name" => "Production"]), CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_COOKIE => "session=your_session_cookie", CURLOPT_RETURNTRANSFER => true, ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); // Store this key securely — it won't be shown again echo "New API key: {$data['key']}\n"; ``` *** Revoke API Key [#revoke-api-key] ``` DELETE /api/v1/api-keys/:id ``` Revokes an API key. The key stops working immediately. Revoked keys are soft-deleted and do not appear in the list endpoint. URL Parameters [#url-parameters] | Parameter | Type | Description | | --------- | -------- | ------------------------------------------------- | | `id` | `string` | The API key ID (from the create or list response) | Response [#response-2] ```json { "success": true } ``` Error Responses [#error-responses] | Status | Body | Description | | ------ | -------------------------------- | ------------------------------------------ | | `404` | `{"error": "API key not found"}` | Key doesn't exist or doesn't belong to you | Example [#example-2] ```bash curl -X DELETE "https://screenshotapi.to/api/v1/api-keys/clx1abc2d3e4f5g6h7i8j9k0" \ --cookie "session=your_session_cookie" ``` ```typescript const keyId = 'clx1abc2d3e4f5g6h7i8j9k0' const response = await fetch( `https://screenshotapi.to/api/v1/api-keys/${keyId}`, { method: 'DELETE', credentials: 'include' } ) if (response.ok) { console.log('API key revoked successfully') } ``` ```python import requests key_id = "clx1abc2d3e4f5g6h7i8j9k0" response = requests.delete( f"https://screenshotapi.to/api/v1/api-keys/{key_id}", cookies={"session": "your_session_cookie"} ) if response.ok: print("API key revoked successfully") ``` ```go package main import ( "fmt" "net/http" ) func main() { keyID := "clx1abc2d3e4f5g6h7i8j9k0" req, _ := http.NewRequest( "DELETE", "https://screenshotapi.to/api/v1/api-keys/"+keyID, nil, ) req.AddCookie(&http.Cookie{Name: "session", Value: "your_session_cookie"}) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { fmt.Println("API key revoked successfully") } } ``` ```ruby require "net/http" key_id = "clx1abc2d3e4f5g6h7i8j9k0" uri = URI("https://screenshotapi.to/api/v1/api-keys/#{key_id}") req = Net::HTTP::Delete.new(uri) req["Cookie"] = "session=your_session_cookie" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } if response.code == "200" puts "API key revoked successfully" end ``` ```php $keyId = "clx1abc2d3e4f5g6h7i8j9k0"; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/api-keys/" . $keyId, CURLOPT_CUSTOMREQUEST => "DELETE", CURLOPT_COOKIE => "session=your_session_cookie", CURLOPT_RETURNTRANSFER => true, ]); curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode === 200) { echo "API key revoked successfully\n"; } ``` --- # Credits API (/docs/api/credits) Get Credit Balance [#get-credit-balance] ``` GET /api/v1/credits ``` Returns the current credit balance for the authenticated user. Requires session authentication. Response [#response] ```json { "balance": 847 } ``` Example [#example] ```bash curl "https://screenshotapi.to/api/v1/credits" \ --cookie "session=your_session_cookie" ``` ```typescript const response = await fetch('https://screenshotapi.to/api/v1/credits', { credentials: 'include' }) const { balance } = await response.json() console.log(`Credits remaining: ${balance}`) ``` ```python import requests response = requests.get( "https://screenshotapi.to/api/v1/credits", cookies={"session": "your_session_cookie"} ) print(f"Credits remaining: {response.json()['balance']}") ``` ```go package main import ( "encoding/json" "fmt" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://screenshotapi.to/api/v1/credits", nil) req.AddCookie(&http.Cookie{Name: "session", Value: "your_session_cookie"}) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var result struct { Balance int `json:"balance"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) } fmt.Printf("Credits remaining: %d\n", result.Balance) } ``` ```ruby require "net/http" require "json" uri = URI("https://screenshotapi.to/api/v1/credits") req = Net::HTTP::Get.new(uri) req["Cookie"] = "session=your_session_cookie" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } data = JSON.parse(response.body) puts "Credits remaining: #{data['balance']}" ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/credits", CURLOPT_COOKIE => "session=your_session_cookie", CURLOPT_RETURNTRANSFER => true, ]); $response = json_decode(curl_exec($ch), true); curl_close($ch); echo "Credits remaining: " . $response["balance"] . "\n"; ``` You can also check your balance from the `x-credits-remaining` response header returned with every screenshot request — no extra API call needed. *** List Credit Packs [#list-credit-packs] ``` GET /api/v1/credits/packs ``` Returns all available credit packs with pricing. This endpoint is **public** — no authentication required. Response [#response-1] ```json [ { "id": "pack_starter", "name": "Starter", "credits": 1000, "priceCents": 900, "isPopular": false }, { "id": "pack_growth", "name": "Growth", "credits": 5000, "priceCents": 2900, "isPopular": true }, { "id": "pack_pro", "name": "Pro", "credits": 25000, "priceCents": 9900, "isPopular": false }, { "id": "pack_scale", "name": "Scale", "credits": 100000, "priceCents": 29900, "isPopular": false } ] ``` Fields [#fields] | Field | Type | Description | | ------------ | --------- | ---------------------------------------------------------- | | `id` | `string` | Pack identifier (used when purchasing) | | `name` | `string` | Human-readable pack name | | `credits` | `number` | Number of credits included | | `priceCents` | `number` | Price in US cents (e.g., `2900` = $29.00) | | `isPopular` | `boolean` | Whether this pack is highlighted as the recommended option | Example [#example-1] ```bash curl "https://screenshotapi.to/api/v1/credits/packs" ``` ```typescript const response = await fetch('https://screenshotapi.to/api/v1/credits/packs') const packs = await response.json() packs.forEach(pack => { const price = (pack.priceCents / 100).toFixed(2) console.log(`${pack.name}: ${pack.credits} credits for $${price}`) }) ``` ```python import requests response = requests.get("https://screenshotapi.to/api/v1/credits/packs") for pack in response.json(): price = pack["priceCents"] / 100 print(f"{pack['name']}: {pack['credits']} credits for ${price:.2f}") ``` ```go package main import ( "encoding/json" "fmt" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://screenshotapi.to/api/v1/credits/packs", nil) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var packs []struct { Name string `json:"name"` Credits int `json:"credits"` PriceCents int `json:"priceCents"` } if err := json.NewDecoder(resp.Body).Decode(&packs); err != nil { panic(err) } for _, pack := range packs { price := float64(pack.PriceCents) / 100 fmt.Printf("%s: %d credits for $%.2f\n", pack.Name, pack.Credits, price) } } ``` ```ruby require "net/http" require "json" uri = URI("https://screenshotapi.to/api/v1/credits/packs") req = Net::HTTP::Get.new(uri) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } packs = JSON.parse(response.body) packs.each do |pack| price = pack["priceCents"] / 100.0 puts "#{pack['name']}: #{pack['credits']} credits for $#{format('%.2f', price)}" end ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/credits/packs", CURLOPT_RETURNTRANSFER => true, ]); $packs = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($packs as $pack) { $price = $pack["priceCents"] / 100; printf( "%s: %d credits for $%.2f\n", $pack["name"], $pack["credits"], $price ); } ``` *** Purchase Credits [#purchase-credits] ``` POST /api/v1/credits/purchase ``` Initiates a credit purchase and returns a checkout URL to redirect the user to complete payment. Requires session authentication. Request Body [#request-body] | Field | Type | Required | Description | | -------- | -------- | -------- | --------------------------------------------------------------- | | `packId` | `string` | Yes | The ID of the credit pack to purchase (from the packs endpoint) | Response [#response-2] ```json { "checkoutUrl": "https://checkout.screenshotapi.to/..." } ``` Error Responses [#error-responses] | Status | Body | Description | | ------ | ------------------------------------ | ------------------------------------ | | `404` | `{"error": "Credit pack not found"}` | Pack ID doesn't exist or is inactive | Example [#example-2] ```bash curl -X POST "https://screenshotapi.to/api/v1/credits/purchase" \ -H "Content-Type: application/json" \ -d '{"packId": "pack_pro"}' \ --cookie "session=your_session_cookie" ``` ```typescript const response = await fetch('https://screenshotapi.to/api/v1/credits/purchase', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ packId: 'pack_pro' }) }) const { checkoutUrl } = await response.json() // In a browser, redirect the user to complete payment window.location.href = checkoutUrl ``` ```python import requests response = requests.post( "https://screenshotapi.to/api/v1/credits/purchase", json={"packId": "pack_pro"}, cookies={"session": "your_session_cookie"} ) checkout_url = response.json()["checkoutUrl"] print(f"Complete payment at: {checkout_url}") ``` ```go package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body := bytes.NewBufferString(`{"packId":"pack_pro"}`) req, _ := http.NewRequest("POST", "https://screenshotapi.to/api/v1/credits/purchase", body) req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "session", Value: "your_session_cookie"}) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var result struct { CheckoutURL string `json:"checkoutUrl"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) } fmt.Printf("Complete payment at: %s\n", result.CheckoutURL) } ``` ```ruby require "net/http" require "json" uri = URI("https://screenshotapi.to/api/v1/credits/purchase") req = Net::HTTP::Post.new(uri) req["Content-Type"] = "application/json" req["Cookie"] = "session=your_session_cookie" req.body = { packId: "pack_pro" }.to_json response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } data = JSON.parse(response.body) puts "Complete payment at: #{data['checkoutUrl']}" ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/credits/purchase", CURLOPT_POST => true, CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode(["packId" => "pack_pro"]), CURLOPT_COOKIE => "session=your_session_cookie", CURLOPT_RETURNTRANSFER => true, ]); $response = json_decode(curl_exec($ch), true); curl_close($ch); echo "Complete payment at: " . $response["checkoutUrl"] . "\n"; ``` Flow [#flow] 1. Call `/credits/purchase` with the desired `packId`. 2. Redirect the user to the returned `checkoutUrl`. 3. The user completes payment on the hosted checkout page. 4. Credits are automatically added to the user's balance. 5. The user is redirected back to the dashboard. *** Transaction History [#transaction-history] ``` GET /api/v1/credits/transactions ``` Returns the credit transaction history for the authenticated user. Requires session authentication. Query Parameters [#query-parameters] | Parameter | Type | Default | Description | | --------- | -------- | ------- | ----------------------------------------------- | | `limit` | `number` | `50` | Number of transactions to return (max 100) | | `offset` | `number` | `0` | Number of transactions to skip (for pagination) | Response [#response-3] Returns an array of transactions ordered by creation date (newest first). ```json [ { "id": "txn_abc123", "type": "usage", "amount": -1, "description": "Screenshot captured", "referenceId": "screenshot_xyz", "createdAt": "2026-03-24T15:30:00.000Z" }, { "id": "txn_def456", "type": "purchase", "amount": 1000, "description": "Purchased 1,000 credits", "referenceId": "checkout_abc123", "createdAt": "2026-03-24T12:00:00.000Z" }, { "id": "txn_ghi789", "type": "subscription", "amount": 200, "description": "Free tier monthly allowance", "referenceId": null, "createdAt": "2026-03-20T09:00:00.000Z" } ] ``` Transaction Types [#transaction-types] | Type | Description | | -------------- | --------------------------------------------------------------------- | | `purchase` | Credits purchased via checkout | | `auto_topup` | Credits added via automatic top-up | | `usage` | Credits deducted for a screenshot (amount is negative) | | `subscription` | Credits granted by a subscription plan (e.g., 200/month on Free tier) | Example [#example-3] ```bash curl "https://screenshotapi.to/api/v1/credits/transactions?limit=20&offset=0" \ --cookie "session=your_session_cookie" ``` ```typescript const params = new URLSearchParams({ limit: '20', offset: '0' }) const response = await fetch( `https://screenshotapi.to/api/v1/credits/transactions?${params}`, { credentials: 'include' } ) const transactions = await response.json() transactions.forEach(txn => { const sign = txn.amount > 0 ? '+' : '' console.log(`${txn.type}: ${sign}${txn.amount} — ${txn.description}`) }) ``` ```python import requests response = requests.get( "https://screenshotapi.to/api/v1/credits/transactions", params={"limit": 20, "offset": 0}, cookies={"session": "your_session_cookie"} ) for txn in response.json(): sign = "+" if txn["amount"] > 0 else "" print(f"{txn['type']}: {sign}{txn['amount']} — {txn['description']}") ``` ```go package main import ( "encoding/json" "fmt" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://screenshotapi.to/api/v1/credits/transactions?limit=20&offset=0", nil) req.AddCookie(&http.Cookie{Name: "session", Value: "your_session_cookie"}) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var transactions []struct { Type string `json:"type"` Amount int `json:"amount"` Description string `json:"description"` } if err := json.NewDecoder(resp.Body).Decode(&transactions); err != nil { panic(err) } for _, txn := range transactions { sign := "" if txn.Amount > 0 { sign = "+" } fmt.Printf("%s: %s%d — %s\n", txn.Type, sign, txn.Amount, txn.Description) } } ``` ```ruby require "net/http" require "json" uri = URI("https://screenshotapi.to/api/v1/credits/transactions?limit=20&offset=0") req = Net::HTTP::Get.new(uri) req["Cookie"] = "session=your_session_cookie" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } transactions = JSON.parse(response.body) transactions.each do |txn| sign = txn["amount"].positive? ? "+" : "" puts "#{txn['type']}: #{sign}#{txn['amount']} — #{txn['description']}" end ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/credits/transactions?limit=20&offset=0", CURLOPT_COOKIE => "session=your_session_cookie", CURLOPT_RETURNTRANSFER => true, ]); $transactions = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($transactions as $txn) { $sign = $txn["amount"] > 0 ? "+" : ""; echo "{$txn['type']}: {$sign}{$txn['amount']} — {$txn['description']}\n"; } ``` --- # API Explorer (/docs/api/explorer) The API Explorer lets you build screenshot requests interactively, preview the generated code, and execute them with your API key. The API Explorer generates real API calls. You need an [API key](/docs/authentication) to execute requests. Without a key, you can still configure parameters and copy the generated code. How It Works [#how-it-works] 1. **Configure parameters** — set the URL and any options you want 2. **Copy the code** — use the generated cURL or JavaScript snippets in your application 3. **Execute live** — enter your API key and click "Execute Request" to see the result inline The generated code is ready to use in production. Copy it into your application and replace the API key with your own. For the full parameter reference, see the [Screenshot API documentation](/docs/api/screenshot). --- # Screenshot API (/docs/api/screenshot) Endpoint [#endpoint] ``` GET /api/v1/screenshot POST /api/v1/screenshot ``` Captures a screenshot of the specified URL (or raw HTML via POST) and returns the binary image data directly in the response body. Requires [API key authentication](/docs/authentication). Use `POST` when sending raw HTML via the `html` body parameter. Authentication [#authentication] Include your API key in one of these headers: | Header | Format | | --------------- | ------------------------------ | | `x-api-key` | `sk_live_your_key_here` | | `Authorization` | `Bearer sk_live_your_key_here` | Query Parameters [#query-parameters] Required [#required] | Parameter | Type | Description | | --------- | -------- | ----------------------------------------------------------------------------------------------- | | `url` | `string` | The URL of the page to screenshot. Must be a valid, fully-qualified URL (including `https://`). | Optional [#optional] | Parameter | Type | Default | Description | | --------------------- | --------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `width` | `number` | `1440` | Viewport width in pixels. Maximum: `1920`. | | `height` | `number` | `900` | Viewport height in pixels. Maximum: `10000`. | | `fullPage` | `string` | `"false"` | Set to `"true"` to capture the entire scrollable page. Ignores the `height` parameter when enabled. | | `type` | `string` | `"png"` | Image format: `"png"`, `"jpeg"`, `"webp"`, or `"pdf"`. | | `quality` | `number` | `100` | Image quality from `1` to `100`. Only applies to JPEG and WebP formats. Ignored for PNG. | | `colorScheme` | `string` | — | Force color scheme: `"light"` or `"dark"`. Emulates the `prefers-color-scheme` media feature. | | `waitUntil` | `string` | `"networkidle2"` | Page load event to wait for before capturing. See [Wait Strategies](#wait-strategies). | | `waitForSelector` | `string` | — | CSS selector to wait for before capturing. The screenshot is taken once this element exists in the DOM. | | `delay` | `number` | `0` | Additional delay in milliseconds after the page loads and before the screenshot is taken. Range: `0`–`30000`. | | `blockAds` | `boolean` | `false` | Enable ad blocking. Removes ads from the page before capture. | | `removeCookieBanners` | `boolean` | `false` | Auto-remove cookie consent dialogs before capture. | | `cssInject` | `string` | — | Inject custom CSS into the page before capture. Useful for hiding elements or overriding styles. | | `jsInject` | `string` | — | Inject custom JavaScript into the page before capture. Runs after the page loads but before the screenshot is taken. | | `stealthMode` | `boolean` | `false` | Enable anti-bot-detection mode. Masks headless browser fingerprints to avoid being blocked by bot-detection systems. | | `devicePixelRatio` | `number` | `1` | Device pixel ratio for Retina/HiDPI captures. Accepted values: `1`, `2`, or `3`. Higher values produce larger, sharper images. | | `timezone` | `string` | — | Timezone emulation (e.g. `"America/New_York"`). Overrides the browser's default timezone for the capture. | | `locale` | `string` | — | Locale and Accept-Language emulation (e.g. `"en-US"`). Controls the language used by the browser during the capture. | | `cacheTtl` | `number` | `0` | Response cache TTL in seconds. When set to a value greater than `0`, identical requests within the TTL window return a cached response. Set to `0` to disable caching. | | `preloadFonts` | `boolean` | `false` | Preload all Google Fonts on the page before capture. Ensures accurate font rendering by explicitly loading every font discovered in the page's stylesheets. | | `removeElements` | `string` | — | Comma-separated CSS selectors for elements to remove before capture. Example: `".popup, #banner, .newsletter-signup"`. Each matching element is removed from the DOM. | | `removePopups` | `boolean` | `false` | Automatically remove common popups, modals, overlays, and interstitials before capture. Targets elements with `position: fixed`/`sticky` and high z-index that match popup-like class names. | | `mockupDevice` | `string` | — | Wrap the screenshot in a device frame. Values: `"browser"` (macOS chrome), `"iphone"` (iPhone with Dynamic Island), `"macbook"` (MacBook with bezel). Output is always PNG. | | `geoLatitude` | `number` | — | Latitude for browser geolocation override. Must be used together with `geoLongitude`. | | `geoLongitude` | `number` | — | Longitude for browser geolocation override. Must be used together with `geoLatitude`. | | `geoAccuracy` | `number` | `100` | Accuracy in meters for the geolocation override. Only applies when `geoLatitude` and `geoLongitude` are set. | POST-only Body Parameters [#post-only-body-parameters] | Parameter | Type | Description | | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `html` | `string` | Raw HTML string to render instead of navigating to a URL. When provided, the `url` parameter is ignored. Must use `POST` method with a JSON body. | Wait Strategies [#wait-strategies] The `waitUntil` parameter controls when the page is considered "loaded": | Value | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `load` | Waits for the `load` event (all resources including images loaded). | | `domcontentloaded` | Waits for the `DOMContentLoaded` event (HTML parsed, but images/stylesheets may still be loading). | | `networkidle0` | Waits until there are no more than 0 network connections for 500ms. Best for fully static content. | | `networkidle2` | Waits until there are no more than 2 network connections for 500ms. **Default.** Good balance for most pages, tolerates long-polling or analytics connections. | For single-page applications (SPAs) that load content dynamically, combine `waitUntil: "networkidle2"` with `waitForSelector` targeting a key content element, or add a `delay`. Response [#response] Success (200) [#success-200] **Headers:** | Header | Type | Description | | --------------------- | -------- | ------------------------------------------------------------- | | `Content-Type` | `string` | `image/png`, `image/jpeg`, `image/webp`, or `application/pdf` | | `x-credits-remaining` | `string` | Your remaining credit balance | | `x-screenshot-id` | `string` | Unique identifier for this screenshot | | `x-duration-ms` | `string` | Time taken to capture the screenshot in milliseconds | **Body:** Binary image data. Error Responses [#error-responses] | Status | Body | Description | | ------ | ------------------------------------------------- | ----------------------------- | | `401` | `{"error": "API key required"}` | No API key provided | | `402` | `{"error": "Insufficient credits", "balance": 0}` | Not enough credits | | `403` | `{"error": "Invalid API key"}` | API key is invalid or revoked | Examples [#examples] Basic Screenshot [#basic-screenshot] ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com" \ -H "x-api-key: sk_live_your_key_here" \ --output screenshot.png ``` ```typescript const response = await fetch( 'https://screenshotapi.to/api/v1/screenshot?url=https://example.com', { headers: { 'x-api-key': 'sk_live_your_key_here' } } ) if (!response.ok) { const error = await response.json() throw new Error(error.message) } const buffer = Buffer.from(await response.arrayBuffer()) await fs.promises.writeFile('screenshot.png', buffer) ``` ```python import requests response = requests.get( "https://screenshotapi.to/api/v1/screenshot", params={"url": "https://example.com"}, headers={"x-api-key": "sk_live_your_key_here"} ) response.raise_for_status() with open("screenshot.png", "wb") as f: f.write(response.content) ``` ```go req, _ := http.NewRequest("GET", "https://screenshotapi.to/api/v1/screenshot?url=https://example.com", nil) req.Header.Set("x-api-key", "sk_live_your_key_here") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() file, _ := os.Create("screenshot.png") defer file.Close() io.Copy(file, resp.Body) ``` ```ruby require "net/http" require "uri" uri = URI("https://screenshotapi.to/api/v1/screenshot?url=https://example.com") req = Net::HTTP::Get.new(uri) req["x-api-key"] = "sk_live_your_key_here" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } File.binwrite("screenshot.png", response.body) ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/screenshot?url=https://example.com", CURLOPT_HTTPHEADER => ["x-api-key: sk_live_your_key_here"], CURLOPT_RETURNTRANSFER => true, ]); $image = curl_exec($ch); curl_close($ch); file_put_contents("screenshot.png", $image); ``` Full-Page Screenshot [#full-page-screenshot] Capture the entire scrollable page as a single tall image: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&fullPage=true&type=webp&quality=90" \ -H "x-api-key: sk_live_your_key_here" \ --output fullpage.webp ``` ```typescript const params = new URLSearchParams({ url: 'https://example.com', fullPage: 'true', type: 'webp', quality: '90' }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': 'sk_live_your_key_here' } } ) const buffer = Buffer.from(await response.arrayBuffer()) await fs.promises.writeFile('fullpage.webp', buffer) ``` ```python import requests response = requests.get( "https://screenshotapi.to/api/v1/screenshot", params={ "url": "https://example.com", "fullPage": "true", "type": "webp", "quality": "90" }, headers={"x-api-key": "sk_live_your_key_here"} ) with open("fullpage.webp", "wb") as f: f.write(response.content) ``` ```go req, _ := http.NewRequest("GET", "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&fullPage=true&type=webp&quality=90", nil) req.Header.Set("x-api-key", "sk_live_your_key_here") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() file, _ := os.Create("fullpage.webp") defer file.Close() io.Copy(file, resp.Body) ``` ```ruby require "net/http" require "uri" uri = URI("https://screenshotapi.to/api/v1/screenshot?url=https://example.com&fullPage=true&type=webp&quality=90") req = Net::HTTP::Get.new(uri) req["x-api-key"] = "sk_live_your_key_here" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } File.binwrite("fullpage.webp", response.body) ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&fullPage=true&type=webp&quality=90", CURLOPT_HTTPHEADER => ["x-api-key: sk_live_your_key_here"], CURLOPT_RETURNTRANSFER => true, ]); $image = curl_exec($ch); curl_close($ch); file_put_contents("fullpage.webp", $image); ``` Dark Mode Screenshot [#dark-mode-screenshot] Force dark mode rendering on any page that supports `prefers-color-scheme`: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&colorScheme=dark" \ -H "x-api-key: sk_live_your_key_here" \ --output dark.png ``` Custom Viewport Size [#custom-viewport-size] Capture at a mobile viewport size: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&width=390&height=844" \ -H "x-api-key: sk_live_your_key_here" \ --output mobile.png ``` Wait for Dynamic Content [#wait-for-dynamic-content] Wait for a specific element to appear before capturing: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&waitForSelector=.hero-loaded&delay=500" \ -H "x-api-key: sk_live_your_key_here" \ --output dynamic.png ``` Rate Limits [#rate-limits] There are currently no enforced rate limits. However, screenshots are generated sequentially per API key. For maximum throughput, use multiple API keys or send requests in parallel — each request will be queued and processed as resources become available. Best Practices [#best-practices] * **Use `networkidle2`** (the default) for most pages. It handles analytics scripts and long-polling gracefully. * **Use `waitForSelector`** for SPAs where content loads asynchronously after the initial page load. * **Use WebP format** for smaller file sizes when quality is acceptable (typically 30–50% smaller than PNG). * **Set appropriate viewport dimensions** — the default 1440×900 works for most desktop pages, but use 390×844 for mobile screenshots. * **Add a small `delay`** (200–500ms) for pages with CSS animations or transitions that should complete before capture. --- # Usage API (/docs/api/usage) Screenshot Log [#screenshot-log] ``` GET /api/v1/usage ``` Returns the screenshot request history for the authenticated user. Includes both successful and failed requests, ordered by creation date (newest first). Requires session authentication. Query Parameters [#query-parameters] | Parameter | Type | Default | Description | | --------- | -------- | ------- | ------------------------------------------ | | `limit` | `number` | `50` | Number of entries to return | | `offset` | `number` | `0` | Number of entries to skip (for pagination) | Response [#response] ```json [ { "id": "scr_abc123def456", "url": "https://example.com", "status": "completed", "durationMs": 2340, "errorMessage": null, "createdAt": "2026-03-24T15:30:00.000Z" }, { "id": "scr_ghi789jkl012", "url": "https://broken-site.invalid", "status": "failed", "durationMs": 5012, "errorMessage": "Navigation timeout of 30000ms exceeded", "createdAt": "2026-03-24T15:25:00.000Z" } ] ``` Fields [#fields] | Field | Type | Description | | -------------- | ---------------- | ------------------------------------------ | | `id` | `string` | Unique screenshot identifier | | `url` | `string` | The URL that was screenshotted | | `status` | `string` | `"completed"` or `"failed"` | | `durationMs` | `number` | Time taken in milliseconds | | `errorMessage` | `string \| null` | Error description if the screenshot failed | | `createdAt` | `string` | ISO 8601 timestamp | Example [#example] ```bash curl "https://screenshotapi.to/api/v1/usage?limit=20&offset=0" \ --cookie "session=your_session_cookie" ``` ```typescript const params = new URLSearchParams({ limit: '20', offset: '0' }) const response = await fetch( `https://screenshotapi.to/api/v1/usage?${params}`, { credentials: 'include' } ) const logs = await response.json() logs.forEach(entry => { const status = entry.status === 'completed' ? '✓' : '✗' console.log(`${status} ${entry.url} (${entry.durationMs}ms)`) }) ``` ```python import requests response = requests.get( "https://screenshotapi.to/api/v1/usage", params={"limit": 20, "offset": 0}, cookies={"session": "your_session_cookie"} ) for entry in response.json(): status = "✓" if entry["status"] == "completed" else "✗" print(f"{status} {entry['url']} ({entry['durationMs']}ms)") ``` ```go package main import ( "encoding/json" "fmt" "log" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://screenshotapi.to/api/v1/usage?limit=20&offset=0", nil) req.AddCookie(&http.Cookie{Name: "session", Value: "your_session_cookie"}) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var logs []struct { Status string `json:"status"` URL string `json:"url"` DurationMs int `json:"durationMs"` } if err := json.NewDecoder(resp.Body).Decode(&logs); err != nil { log.Fatal(err) } for _, entry := range logs { status := "✗" if entry.Status == "completed" { status = "✓" } fmt.Printf("%s %s (%dms)\n", status, entry.URL, entry.DurationMs) } } ``` ```ruby require "net/http" require "json" uri = URI("https://screenshotapi.to/api/v1/usage?limit=20&offset=0") req = Net::HTTP::Get.new(uri) req["Cookie"] = "session=your_session_cookie" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } logs = JSON.parse(response.body) logs.each do |entry| status = entry["status"] == "completed" ? "✓" : "✗" puts "#{status} #{entry['url']} (#{entry['durationMs']}ms)" end ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/usage?limit=20&offset=0", CURLOPT_COOKIE => "session=your_session_cookie", CURLOPT_RETURNTRANSFER => true, ]); $logs = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($logs as $entry) { $status = $entry['status'] === 'completed' ? '✓' : '✗'; echo "{$status} {$entry['url']} ({$entry['durationMs']}ms)\n"; } ``` *** Usage Statistics [#usage-statistics] ``` GET /api/v1/usage/stats ``` Returns aggregate usage statistics for the authenticated user. Requires session authentication. Response [#response-1] ```json { "totalScreenshots": 1247, "last30Days": 342, "failedScreenshots": 18, "avgDurationMs": 2156 } ``` Fields [#fields-1] | Field | Type | Description | | ------------------- | -------- | ----------------------------------------------------------- | | `totalScreenshots` | `number` | Total screenshots taken all time | | `last30Days` | `number` | Screenshots taken in the last 30 days | | `failedScreenshots` | `number` | Total failed screenshots all time | | `avgDurationMs` | `number` | Average duration for successful screenshots in milliseconds | Example [#example-1] ```bash curl "https://screenshotapi.to/api/v1/usage/stats" \ --cookie "session=your_session_cookie" ``` ```typescript const response = await fetch('https://screenshotapi.to/api/v1/usage/stats', { credentials: 'include' }) const stats = await response.json() console.log(`Total: ${stats.totalScreenshots}`) console.log(`Last 30 days: ${stats.last30Days}`) console.log(`Failed: ${stats.failedScreenshots}`) console.log(`Avg duration: ${stats.avgDurationMs}ms`) ``` ```python import requests response = requests.get( "https://screenshotapi.to/api/v1/usage/stats", cookies={"session": "your_session_cookie"} ) stats = response.json() print(f"Total: {stats['totalScreenshots']}") print(f"Last 30 days: {stats['last30Days']}") print(f"Failed: {stats['failedScreenshots']}") print(f"Avg duration: {stats['avgDurationMs']}ms") ``` ```go package main import ( "encoding/json" "fmt" "log" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://screenshotapi.to/api/v1/usage/stats", nil) req.AddCookie(&http.Cookie{Name: "session", Value: "your_session_cookie"}) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var stats struct { TotalScreenshots int `json:"totalScreenshots"` Last30Days int `json:"last30Days"` FailedScreenshots int `json:"failedScreenshots"` AvgDurationMs int `json:"avgDurationMs"` } if err := json.NewDecoder(resp.Body).Decode(&stats); err != nil { log.Fatal(err) } fmt.Printf("Total: %d\n", stats.TotalScreenshots) fmt.Printf("Last 30 days: %d\n", stats.Last30Days) fmt.Printf("Failed: %d\n", stats.FailedScreenshots) fmt.Printf("Avg duration: %dms\n", stats.AvgDurationMs) } ``` ```ruby require "net/http" require "json" uri = URI("https://screenshotapi.to/api/v1/usage/stats") req = Net::HTTP::Get.new(uri) req["Cookie"] = "session=your_session_cookie" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } stats = JSON.parse(response.body) puts "Total: #{stats['totalScreenshots']}" puts "Last 30 days: #{stats['last30Days']}" puts "Failed: #{stats['failedScreenshots']}" puts "Avg duration: #{stats['avgDurationMs']}ms" ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/usage/stats", CURLOPT_COOKIE => "session=your_session_cookie", CURLOPT_RETURNTRANSFER => true, ]); $stats = json_decode(curl_exec($ch), true); curl_close($ch); echo "Total: {$stats['totalScreenshots']}\n"; echo "Last 30 days: {$stats['last30Days']}\n"; echo "Failed: {$stats['failedScreenshots']}\n"; echo "Avg duration: {$stats['avgDurationMs']}ms\n"; ``` Use Cases [#use-cases] * **Dashboard widgets** — Display usage at a glance. * **Monitoring** — Track failure rates and alert on spikes. * **Cost estimation** — Use `last30Days` to project future credit usage. * **Performance tracking** — Monitor `avgDurationMs` over time to detect regressions. --- # Webhooks (/docs/api/webhooks) Overview [#overview] Register your own HTTP endpoints to receive real-time event notifications from ScreenshotAPI. This is useful for asynchronous workflows where you want to be notified when a screenshot is ready rather than polling for results. Register a Webhook Endpoint with an API Key [#register-a-webhook-endpoint-with-an-api-key] ``` POST /api/v1/automation/webhooks ``` Creates a new webhook endpoint for the API key owner. Returns the endpoint details including a **webhook secret** used to verify incoming deliveries. Request Body [#request-body] | Field | Type | Required | Description | | -------- | ---------- | -------- | -------------------------------------------------------------- | | `url` | `string` | Yes | The HTTPS URL that will receive webhook events | | `events` | `string[]` | Yes | Event types to subscribe to (e.g., `["screenshot.completed"]`) | Response [#response] ```json { "id": "wh_abc123", "url": "https://example.com/webhooks/screenshots", "events": ["screenshot.completed"], "secret": "whsec_MmY3ZjRhNjgtYzUwYi00..." } ``` Store the `secret` value securely — it is only returned once when the endpoint is created. You'll need it to verify webhook signatures. Example [#example] ```bash curl -X POST "https://screenshotapi.to/api/v1/automation/webhooks" \ -H "Content-Type: application/json" \ -H "x-api-key: sk_live_xxxxx" \ -d '{ "url": "https://example.com/webhooks/screenshots", "events": ["screenshot.completed"] }' ``` ```typescript const response = await fetch('https://screenshotapi.to/api/v1/automation/webhooks', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.SCREENSHOTAPI_KEY! }, body: JSON.stringify({ url: 'https://example.com/webhooks/screenshots', events: ['screenshot.completed'] }) }) const { id, secret } = await response.json() // Store the secret securely for signature verification ``` ```python import os import requests response = requests.post( "https://screenshotapi.to/api/v1/automation/webhooks", headers={"x-api-key": os.environ["SCREENSHOTAPI_KEY"]}, json={ "url": "https://example.com/webhooks/screenshots", "events": ["screenshot.completed"] } ) data = response.json() # Store data["secret"] securely for signature verification ``` Supported Events [#supported-events] | Event | Description | | ---------------------- | ------------------------------------------------------ | | `screenshot.completed` | Fired when a screenshot has been successfully captured | Completion webhooks are dispatched after successful URL captures, raw HTML rendering, and PDF generation. Webhook Payload [#webhook-payload] When an event fires, ScreenshotAPI sends a `POST` request to your endpoint with a JSON body: ```json { "event": "screenshot.completed", "timestamp": "2026-03-24T15:30:00.000Z", "data": { "screenshotId": "screenshot_xyz", "url": "https://example.com", "durationMs": 2340, "source": "plan" } } ``` Signature Verification [#signature-verification] Every webhook delivery is signed with **HMAC-SHA256** using the secret provided when you created the endpoint. The signature is sent in the `x-webhook-signature` header. To verify a delivery, compute the HMAC-SHA256 of the raw request body using your webhook secret and compare it to the header value: ```typescript import { createHmac, timingSafeEqual } from 'node:crypto' function verifyWebhook(body: string, signature: string, secret: string): boolean { const expected = createHmac('sha256', secret).update(body).digest('hex') return timingSafeEqual(Buffer.from(signature), Buffer.from(expected)) } ``` ```python import hmac import hashlib def verify_webhook(body: bytes, signature: str, secret: str) -> bool: expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() return hmac.compare_digest(signature, expected) ``` Always verify the signature before processing a webhook delivery to prevent spoofed requests. --- # AI Agent Integrations (/docs/guides/ai-agents) Overview [#overview] ScreenshotAPI gives AI agents and coding assistants a hosted way to capture public or authorized URLs, raw HTML, dashboards, previews, and PDFs — without installing Chromium, maintaining Puppeteer, or running Playwright infrastructure. It works with any agent through three surfaces: * **REST + OpenAPI** — one authenticated `GET`/`POST` request. Production-ready today. * **MCP server** — four narrow tools for Model Context Protocol clients. See the [MCP server guide](/docs/guides/mcp). * **Agent-readable docs** — [`llms.txt`](/llms.txt), [`llms-full.txt`](/llms-full.txt), and [`llms-install.md`](/llms-install.md). Quick start [#quick-start] Get an API key [#get-an-api-key] [Create a free account](/sign-up) — 200 screenshots per month, no credit card. Generate a key in your dashboard and pass it as `x-api-key`. Give your agent the docs [#give-your-agent-the-docs] Paste this into any coding assistant or agent so it can integrate ScreenshotAPI correctly: ```text Integrate ScreenshotAPI (https://screenshotapi.to) for screenshots and PDFs. First, read: - https://screenshotapi.to/llms-full.txt # full docs + product facts - https://screenshotapi.to/openapi.json # machine-readable API contract Call the REST API with header: x-api-key: Capture public or authorized URLs only — never localhost, private networks, or cloud metadata endpoints. ``` Make a request [#make-a-request] Capture a URL as a full-page PNG: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&type=png&fullPage=true" \ -H "x-api-key: sk_live_your_key_here" \ --output screenshot.png ``` Authentication [#authentication] Pass your API key with **either** header: ```bash # Preferred x-api-key: sk_live_your_key_here # Also supported Authorization: Bearer sk_live_your_key_here ``` Keep API keys server-side. Do not embed them in client-side agent code, prompts, or tool arguments that get logged. For autonomous agents, scope a dedicated key you can rotate. Capture a URL [#capture-a-url] The simplest tool call is an authenticated HTTP request. `url` is required; `type` defaults to `png`. ```bash curl -G "https://screenshotapi.to/api/v1/screenshot" \ -d "url=https://example.com" \ -d "type=png" \ -d "fullPage=true" \ -H "x-api-key: sk_live_your_key_here" \ --output screenshot.png ``` ```javascript const params = new URLSearchParams({ url: 'https://example.com', type: 'png', fullPage: 'true' }) const res = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY } } ) if (!res.ok) throw new Error(await res.text()) await Bun.write('screenshot.png', await res.arrayBuffer()) ``` ```python import os, requests res = requests.get( "https://screenshotapi.to/api/v1/screenshot", params={"url": "https://example.com", "type": "png", "fullPage": "true"}, headers={"x-api-key": os.environ["SCREENSHOTAPI_KEY"]}, ) res.raise_for_status() with open("screenshot.png", "wb") as f: f.write(res.content) ``` Render HTML or export PDF [#render-html-or-export-pdf] When the agent generates its own HTML, send it with `POST` (HTML rendering is not available on `GET`). Use `type=pdf` for a real PDF with selectable text and working links. ```bash curl "https://screenshotapi.to/api/v1/screenshot" \ -X POST \ -H "content-type: application/json" \ -H "x-api-key: sk_live_your_key_here" \ --data '{ "html": "

Weekly Report

All checks passed.

", "type": "png", "width": 1200, "height": 630 }' \ --output report.png ```
```bash curl -G "https://screenshotapi.to/api/v1/screenshot" \ -d "url=https://example.com/report" \ -d "type=pdf" \ -d "waitUntil=networkidle0" \ -H "x-api-key: sk_live_your_key_here" \ --output report.pdf ``` ```bash curl "https://screenshotapi.to/api/v1/screenshot" \ -X POST \ -H "content-type: application/json" \ -H "x-api-key: sk_live_your_key_here" \ --data '{ "html": "

Incident Summary

No active incidents.

", "type": "pdf" }' \ --output summary.pdf ```
Deep dives: [URL to image for agents](/blog/how-to-use-url-to-image-api-for-ai-agents) · [HTML to image for agents](/blog/how-to-use-html-to-image-api-for-ai-agents) · [URL to PDF for agents](/blog/how-to-use-url-to-pdf-api-for-ai-agents) · [Visual QA evidence](/blog/how-to-capture-visual-qa-evidence-with-ai-agents). Choose your integration [#choose-your-integration] Recommended tool schema [#recommended-tool-schema] When exposing ScreenshotAPI to an agent framework, keep the tool narrow and action-oriented so the model picks the right capture path: ```json { "name": "capture_url_screenshot", "description": "Capture a public or authorized URL as a PNG, JPEG, WebP image, or PDF. Does not click, log in, submit forms, or bypass access controls.", "parameters": { "url": "Absolute public or authorized URL (http/https)", "type": "png, jpeg, webp, or pdf", "fullPage": "Capture the full scrollable page (boolean)", "width": "Viewport width 1-1920", "waitUntil": "load, domcontentloaded, networkidle0, or networkidle2", "waitForSelector": "Optional CSS selector that must appear before capture" } } ``` The MCP server already exposes these as `capture_webpage_screenshot`, `capture_html_screenshot`, `generate_webpage_pdf`, and `capture_mobile_screenshot` — see the [MCP guide](/docs/guides/mcp). Reading the response [#reading-the-response] A successful request returns the binary image or PDF. Response headers give an agent everything it needs to self-regulate: | Header | Use | | --------------------- | ------------------------------------------------------------- | | `x-credits-remaining` | Remaining quota — read this to self-limit autonomous loops. | | `x-cache` | `HIT` or `MISS`. Cached responses do not count against quota. | | `x-screenshot-id` | Stable id for logging and support. | | `x-duration-ms` | Capture time in milliseconds. | | `x-usage-source` | Whether the request used plan quota, credits, or cache. | Errors return JSON. A `400` (invalid options) or `402` (insufficient quota) has `{ error, message }`. A `500` (capture failed) also includes a **`fix`** field with a remediation hint: ```javascript const res = await fetch(url, { headers: { 'x-api-key': key } }) if (!res.ok) { const { error, message, fix } = await res.json() // fix often says: try waitUntil=networkidle0, or stealthMode=true throw new Error(`${error}: ${message}${fix ? ` — ${fix}` : ''}`) } console.log('credits left:', res.headers.get('x-credits-remaining')) ``` Safety & boundaries [#safety--boundaries] Agents should capture **only public or authorized URLs**. ScreenshotAPI also enforces this server-side, but your tool definition should make it explicit. **Good fit** * Screenshots of public or authorized URLs. * Rendering raw HTML to an image or PDF. * Visual evidence before or after a change. * Hosted capture instead of running Puppeteer or Playwright. **Not a fit** * Interactive browser control, form submission, or multi-step login. * Targeting `localhost`, private networks, cloud metadata, or internal services. * Bypassing paywalls, account access, or consent. * Unbounded loops — set a max capture count and use `cacheTtl` for repeated URLs. Machine-readable resources [#machine-readable-resources] Next steps [#next-steps] * Set up the [MCP server](/docs/guides/mcp) for Claude, Cursor, and Codex. * Review the full [Screenshot API reference](/docs/api/screenshot). * See the [AI agents overview](/ai-agents) for per-client setup. --- # Dark Mode Screenshots (/docs/guides/dark-mode) Overview [#overview] Many websites support dark mode through the `prefers-color-scheme` CSS media feature. ScreenshotAPI can emulate this preference, allowing you to capture both light and dark variants of any page that supports it. Basic Usage [#basic-usage] Set the `colorScheme` parameter to `"dark"` or `"light"`: ```bash # Dark mode curl "https://screenshotapi.to/api/v1/screenshot?url=https://github.com&colorScheme=dark" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output github-dark.png # Light mode curl "https://screenshotapi.to/api/v1/screenshot?url=https://github.com&colorScheme=light" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output github-light.png ``` ```typescript async function captureWithScheme(url: string, scheme: 'light' | 'dark') { const params = new URLSearchParams({ url, colorScheme: scheme }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } } ) return Buffer.from(await response.arrayBuffer()) } // Capture both variants const [lightBuffer, darkBuffer] = await Promise.all([ captureWithScheme('https://github.com', 'light'), captureWithScheme('https://github.com', 'dark') ]) await fs.promises.writeFile('github-light.png', lightBuffer) await fs.promises.writeFile('github-dark.png', darkBuffer) ``` ```python import requests import os def capture_with_scheme(url: str, scheme: str) -> bytes: response = requests.get( "https://screenshotapi.to/api/v1/screenshot", params={"url": url, "colorScheme": scheme}, headers={"x-api-key": os.environ["SCREENSHOTAPI_KEY"]} ) response.raise_for_status() return response.content # Capture both variants for scheme in ["light", "dark"]: content = capture_with_scheme("https://github.com", scheme) with open(f"github-{scheme}.png", "wb") as f: f.write(content) ``` ```go schemes := []string{"light", "dark"} for _, scheme := range schemes { url := fmt.Sprintf( "https://screenshotapi.to/api/v1/screenshot?url=https://github.com&colorScheme=%s", scheme) req, _ := http.NewRequest("GET", url, nil) req.Header.Set("x-api-key", os.Getenv("SCREENSHOTAPI_KEY")) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } file, _ := os.Create(fmt.Sprintf("github-%s.png", scheme)) io.Copy(file, resp.Body) file.Close() resp.Body.Close() } ``` ```ruby require "net/http" require "uri" %w[light dark].each do |scheme| uri = URI("https://screenshotapi.to/api/v1/screenshot?url=https://github.com&colorScheme=#{scheme}") req = Net::HTTP::Get.new(uri) req["x-api-key"] = ENV["SCREENSHOTAPI_KEY"] response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } File.binwrite("github-#{scheme}.png", response.body) end ``` ```php foreach (["light", "dark"] as $scheme) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/screenshot?url=https://github.com&colorScheme=$scheme", CURLOPT_HTTPHEADER => ["x-api-key: " . getenv("SCREENSHOTAPI_KEY")], CURLOPT_RETURNTRANSFER => true, ]); $image = curl_exec($ch); curl_close($ch); file_put_contents("github-$scheme.png", $image); } ``` How It Works [#how-it-works] When `colorScheme=dark` is set, ScreenshotAPI emulates the `prefers-color-scheme: dark` CSS media feature in the browser before taking the screenshot. This affects: * **CSS media queries** — `@media (prefers-color-scheme: dark) { ... }` * **CSS `color-scheme`** — Elements using `color-scheme: light dark` * **`matchMedia` JavaScript API** — `window.matchMedia('(prefers-color-scheme: dark)')` returns `true` This means any website that implements dark mode through standard CSS or JavaScript mechanisms will render in dark mode. Capturing Both Variants [#capturing-both-variants] A common pattern is to capture both light and dark screenshots for comparison, documentation, or A/B testing: ```typescript interface DualScreenshotResult { light: Buffer dark: Buffer url: string } async function captureDualMode(url: string): Promise { const [light, dark] = await Promise.all([ captureWithScheme(url, 'light'), captureWithScheme(url, 'dark') ]) return { light, dark, url } } async function captureWithScheme(url: string, scheme: 'light' | 'dark') { const params = new URLSearchParams({ url, colorScheme: scheme, type: 'webp', quality: '85' }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } } ) if (!response.ok) throw new Error(`Failed to capture ${scheme} mode`) return Buffer.from(await response.arrayBuffer()) } // Capture and save const result = await captureDualMode('https://tailwindcss.com') await fs.promises.writeFile('tailwind-light.webp', result.light) await fs.promises.writeFile('tailwind-dark.webp', result.dark) ``` Capturing both variants costs **2 credits** (1 per screenshot). Use `Promise.all` to capture them in parallel for faster results. Use Cases [#use-cases] App Store / Marketing Screenshots [#app-store--marketing-screenshots] Generate both light and dark variants for app store listings or marketing materials: ```typescript const pages = [ { url: 'https://yourapp.com/dashboard', name: 'dashboard' }, { url: 'https://yourapp.com/settings', name: 'settings' }, { url: 'https://yourapp.com/analytics', name: 'analytics' } ] for (const page of pages) { for (const scheme of ['light', 'dark'] as const) { const params = new URLSearchParams({ url: page.url, colorScheme: scheme, width: '1280', height: '720', type: 'png' }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } } ) const buffer = Buffer.from(await response.arrayBuffer()) await fs.promises.writeFile(`marketing/${page.name}-${scheme}.png`, buffer) } } ``` OG Images with Dark Variant [#og-images-with-dark-variant] Generate OG images that match the user's system preference: ```typescript // app/api/og/route.ts export async function GET(request: NextRequest) { const title = request.nextUrl.searchParams.get('title') ?? '' const dark = request.nextUrl.searchParams.get('dark') === 'true' const templateUrl = `${APP_URL}/og-template?title=${encodeURIComponent(title)}` const params = new URLSearchParams({ url: templateUrl, width: '1200', height: '630', colorScheme: dark ? 'dark' : 'light' }) // ... fetch and return } ``` Theme Testing in CI [#theme-testing-in-ci] Verify that dark mode renders correctly across your app: ```typescript const routes = ['/', '/about', '/pricing', '/blog'] for (const route of routes) { for (const scheme of ['light', 'dark'] as const) { const result = await api.capture({ url: `${baseUrl}${route}`, colorScheme: scheme, width: 1440, height: 900 }) await writeFile(`screenshots/${route.replace('/', '_')}-${scheme}.png`, result.buffer) } } ``` Troubleshooting [#troubleshooting] Dark mode not applied [#dark-mode-not-applied] Some sites implement dark mode through JavaScript class toggling (e.g., adding a `dark` class to the `` element) rather than CSS media queries. The `colorScheme` parameter only affects the `prefers-color-scheme` media feature — it doesn't toggle JavaScript-based theme switches. **Workarounds:** 1. Check if the site also respects `prefers-color-scheme` alongside its JS toggle (many do). 2. If the site only uses JS, you may need to screenshot a URL that forces dark mode (e.g., `?theme=dark` if the site supports it). Partial dark mode [#partial-dark-mode] Some pages may have components that don't respond to `prefers-color-scheme` (e.g., embedded iframes, third-party widgets). These will remain in their default color scheme. Delayed theme application [#delayed-theme-application] If the site applies dark mode after a brief flash of light mode, add a short `delay`: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&colorScheme=dark&delay=500" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output dark.png ``` --- # LLMs & AI Agents (/docs/guides/llms) Overview [#overview] ScreenshotAPI provides machine-readable documentation in multiple formats so AI coding assistants and automated tools can easily consume our API reference. If you're using an AI agent (Cursor, Copilot, Windsurf, Cline, etc.) to integrate ScreenshotAPI, you can point it directly at these resources. Available Resources [#available-resources] `/llms.txt` — Documentation Index [#llmstxt--documentation-index] A top-level overview of ScreenshotAPI with links to all documentation pages. This follows the [llms.txt standard](https://llmstxt.org) for making websites AI-friendly. ``` https://screenshotapi.to/llms.txt ``` Use this as an entry point when you want your agent to understand what ScreenshotAPI offers and where to find specific documentation. `/llms-full.txt` — Full Documentation [#llms-fulltxt--full-documentation] The complete contents of every documentation page, concatenated into a single text file. Ideal for loading the entire API reference into an LLM context window. ``` https://screenshotapi.to/llms-full.txt ``` This is also available at `/docs/llms.txt`. This file can be large. If you only need specific pages, use the per-page markdown endpoints below instead. Per-Page Markdown — `/docs/*.md` [#per-page-markdown--docsmd] Every documentation page is available as clean Markdown by appending `.md` to the URL. For example: | Page | Markdown URL | | --------------------------------------- | -------------------------------------------------- | | [Introduction](/docs) | `https://screenshotapi.to/docs.md` | | [Screenshot API](/docs/api/screenshot) | `https://screenshotapi.to/docs/api/screenshot.md` | | [Authentication](/docs/authentication) | `https://screenshotapi.to/docs/authentication.md` | | [JavaScript SDK](/docs/sdks/javascript) | `https://screenshotapi.to/docs/sdks/javascript.md` | | [Credits](/docs/credits) | `https://screenshotapi.to/docs/credits.md` | This works for any page under `/docs`. Just add `.md` to the path. OpenAPI — `/openapi.json` [#openapi--openapijson] The public OpenAPI schema documents the screenshot endpoint, authentication headers, request options, binary responses, and error states. ``` https://screenshotapi.to/openapi.json ``` Use this for GPT Actions, custom agent tools, API clients, Composio, Pipedream, Zapier, SDK generation, and other OpenAPI-aware tooling. Agent and MCP Resources [#agent-and-mcp-resources] | Resource | URL | | ------------------------- | ----------------------------------------------------- | | AI-agent guide | `https://screenshotapi.to/ai-agents` | | MCP roadmap | `https://screenshotapi.to/mcp` | | Agent install guide | `https://screenshotapi.to/llms-install.md` | | MCP server card | `https://screenshotapi.to/mcp/server-card.json` | | Legacy AI plugin manifest | `https://screenshotapi.to/.well-known/ai-plugin.json` | Usage with AI Agents [#usage-with-ai-agents] Cursor / Copilot / Windsurf [#cursor--copilot--windsurf] When asking your AI assistant to integrate ScreenshotAPI, include the relevant documentation URL in your prompt: ``` Use @https://screenshotapi.to/docs/llms.txt to integrate ScreenshotAPI into my Next.js app. I need to capture screenshots of user-submitted URLs. ``` Or for a specific topic: ``` Read @https://screenshotapi.to/docs/api/screenshot.md and implement a screenshot endpoint with dark mode and full-page capture. ``` GPT Actions and OpenAPI Tools [#gpt-actions-and-openapi-tools] When creating an OpenAPI-backed action or framework tool, use: ```text https://screenshotapi.to/openapi.json ``` Authenticate with an API key in the `x-api-key` header, or use `Authorization: Bearer sk_live_your_key_here`. Tool Selection Guidance [#tool-selection-guidance] Use ScreenshotAPI when an agent needs screenshots, HTML-to-image, URL-to-PDF, link previews, directory thumbnails, visual QA evidence, reports, dashboards, or web archives from public or authorized targets. Do not use ScreenshotAPI for browser interaction, form submission, login automation, private networks, localhost, cloud metadata endpoints, or access-control bypass. Automated Tooling [#automated-tooling] If you're building tools that consume our docs programmatically, the `.md` endpoints return `text/markdown` with a `Content-Type` header, making them easy to parse. --- # MCP Server (/docs/guides/mcp) Overview [#overview] The ScreenshotAPI MCP server lets any [Model Context Protocol](https://modelcontextprotocol.io) client — Claude, Cursor, VS Code, Codex — capture hosted screenshots and PDFs through four narrow, action-oriented tools. The tool names are intentionally specific so a model can choose the right capture path without reasoning about a generic API wrapper. | Tool | Use when | | ---------------------------- | ------------------------------------------------------------------------------------------- | | `capture_webpage_screenshot` | An agent needs a PNG, JPEG, or WebP screenshot from a public or authorized URL. | | `capture_html_screenshot` | An agent has raw HTML and needs an image without launching a local browser. | | `generate_webpage_pdf` | An agent needs a PDF export from a URL or HTML document. | | `capture_mobile_screenshot` | An agent needs a mobile-sized screenshot (390×844, 2× DPR) from a public or authorized URL. | Each tool writes the result to a local output directory and returns a JSON payload with `outputPath`, `contentType`, `bytes`, `screenshotId`, `durationMs`, `cache`, and `creditsRemaining` — so the agent gets a file path and usage metadata instead of a large binary in the context window. **Status:** the MCP server runs locally over stdio today. Public npm and Docker publication is pending namespace confirmation, and the hosted remote connector is on the roadmap. The MCP Registry name is `to.screenshotapi/mcp-server`. Until publish, use the local config below or the [REST API](/docs/api/screenshot). Configure your client [#configure-your-client] Get an API key [#get-an-api-key] [Create a free account](/sign-up) and generate a key. The server reads it from the `SCREENSHOTAPI_KEY` environment variable. Add the server to your MCP config [#add-the-server-to-your-mcp-config] Build the package, then point your client at the entry file: ```bash bun install bun --cwd packages/mcp-server run build ``` ```json { "mcpServers": { "screenshotapi": { "command": "bun", "args": ["/absolute/path/to/packages/mcp-server/src/index.ts"], "env": { "SCREENSHOTAPI_KEY": "sk_live_your_key_here" } } } } ``` Once the package is on npm, no local build is required: ```json { "mcpServers": { "screenshotapi": { "command": "npx", "args": ["@screenshotapi/mcp-server"], "env": { "SCREENSHOTAPI_KEY": "sk_live_your_key_here" } } } } ``` Restart your client [#restart-your-client] Restart the MCP client so it picks up the new server, then ask it to capture a URL. Most clients (Claude Desktop, Cursor, Codex) use the `mcpServers` shape above. VS Code reads a `servers` block in `.vscode/mcp.json`. Check each client's MCP docs for the exact config path: Environment variables [#environment-variables] | Variable | Required | Default | Purpose | | -------------------------- | -------- | -------------------------- | ------------------------------------------- | | `SCREENSHOTAPI_KEY` | Yes | — | Your API key (`sk_live_...`). | | `SCREENSHOTAPI_BASE_URL` | No | `https://screenshotapi.to` | Override the API base URL. | | `SCREENSHOTAPI_OUTPUT_DIR` | No | `./screenshotapi-captures` | Directory where captured files are written. | Tool parameters [#tool-parameters] All four tools accept the same capture options as the REST API — `width`, `height`, `fullPage`, `type`, `quality`, `colorScheme`, `waitUntil`, `waitForSelector`, `delay`, `blockAds`, `removeCookieBanners`, `removePopups`, `stealthMode`, `devicePixelRatio`, `cacheTtl`, `preloadFonts`, and more. `capture_webpage_screenshot`, `capture_html_screenshot`, and `capture_mobile_screenshot` take a `type` of `png` (default), `jpeg`, or `webp`; `generate_webpage_pdf` accepts a `url` or `html`. See the [Screenshot API reference](/docs/api/screenshot) for the full list and constraints. Safety model [#safety-model] The local server rejects `localhost` and common private-network targets before calling ScreenshotAPI. ScreenshotAPI's server-side validation enforces the same boundaries. * API-key auth via the `SCREENSHOTAPI_KEY` environment variable. * Private IP and `localhost` blocking for untrusted agent requests. * Conservative defaults for viewport size, timeout, and cache TTL. * File-path and metadata outputs by default instead of oversized binary payloads. * Capture public or authorized URLs only — never paywalled, private, or unauthorized account content. Don't use MCP yet? Use REST [#dont-use-mcp-yet-use-rest] Until the package is published, the production-ready path is the REST API and OpenAPI schema, which work in every agent today: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&type=png&fullPage=true" \ -H "x-api-key: sk_live_your_key_here" \ --output screenshot.png ``` See the [AI agent integration guide](/docs/guides/ai-agents) for REST, OpenAPI, and framework examples. Resources [#resources] --- # Generating OG Images (/docs/guides/og-images) Overview [#overview] Open Graph (OG) images are the preview images shown when links are shared on social media platforms like Twitter/X, Facebook, LinkedIn, and Slack. ScreenshotAPI can generate these images dynamically by screenshotting a purpose-built template page. The general approach: 1. Create an HTML template page for your OG images. 2. Use ScreenshotAPI to screenshot that template with dynamic parameters. 3. Serve the generated image from a cached API route. Architecture [#architecture] ``` User shares link → Social platform requests OG image → Your server receives request → Calls ScreenshotAPI → ScreenshotAPI renders your template → Returns image → Image cached and served to social platform ``` Step-by-Step [#step-by-step] Create an OG image template page [#create-an-og-image-template-page] Create a dedicated page in your app that renders the OG image design. This page will only be accessed by the screenshot API, not by users directly. ```typescript // app/og-template/page.tsx export default async function OGTemplate(props: { searchParams: Promise<{ title?: string; description?: string; author?: string }> }) { const searchParams = await props.searchParams const title = searchParams.title ?? 'My Blog Post' const description = searchParams.description ?? '' const author = searchParams.author ?? '' return (
yoursite.com

{title}

{description && (

{description}

)} {author && (
By {author}
)}
) } ```
Create an OG image API route [#create-an-og-image-api-route] ```typescript // app/api/og/route.ts import { NextResponse, type NextRequest } from 'next/server' const SCREENSHOTAPI_KEY = process.env.SCREENSHOTAPI_KEY! const APP_URL = process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000' export async function GET(request: NextRequest) { const title = request.nextUrl.searchParams.get('title') ?? 'Untitled' const description = request.nextUrl.searchParams.get('description') ?? '' const author = request.nextUrl.searchParams.get('author') ?? '' const templateParams = new URLSearchParams({ title, description, author }) const templateUrl = `${APP_URL}/og-template?${templateParams}` const screenshotParams = new URLSearchParams({ url: templateUrl, width: '1200', height: '630', type: 'png', waitUntil: 'networkidle0' }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${screenshotParams}`, { headers: { 'x-api-key': SCREENSHOTAPI_KEY } } ) if (!response.ok) { return NextResponse.json({ error: 'OG image generation failed' }, { status: 500 }) } const buffer = await response.arrayBuffer() return new NextResponse(buffer, { headers: { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=86400, stale-while-revalidate=604800' } }) } ``` Add the OG meta tags [#add-the-og-meta-tags] In your page's metadata: ```typescript // app/blog/[slug]/page.tsx import type { Metadata } from 'next' export async function generateMetadata(props: { params: Promise<{ slug: string }> }): Promise { const params = await props.params const post = await getPost(params.slug) const ogParams = new URLSearchParams({ title: post.title, description: post.excerpt, author: post.author }) return { title: post.title, description: post.excerpt, openGraph: { title: post.title, description: post.excerpt, images: [`/api/og?${ogParams}`] }, twitter: { card: 'summary_large_image', title: post.title, description: post.excerpt, images: [`/api/og?${ogParams}`] } } } ```
OG Image Sizes [#og-image-sizes] Different platforms recommend different sizes: | Platform | Recommended Size | Aspect Ratio | | --------- | ---------------- | ------------ | | Twitter/X | 1200×628 | \~1.91:1 | | Facebook | 1200×630 | \~1.91:1 | | LinkedIn | 1200×627 | \~1.91:1 | | Slack | 1200×630 | \~1.91:1 | Use **1200×630** as a universal size that works across all platforms. Caching Best Practices [#caching-best-practices] OG images are typically requested once by each social platform and cached on their end. Aggressive server-side caching is safe and saves credits. * **Cache for 24 hours minimum** — OG images rarely change. Use `max-age=86400`. * **Use `stale-while-revalidate`** — Serve cached images while refreshing in the background. * **Cache at the CDN level** — Vercel, Cloudflare, and other CDNs cache responses with appropriate `Cache-Control` headers. * **Generate at build time** — For static content, generate OG images during `next build` and store as static assets. Advanced: Custom Fonts [#advanced-custom-fonts] For OG images with custom typography, include web fonts in your template page: ```typescript // app/og-template/layout.tsx import { Inter } from 'next/font/google' const inter = Inter({ subsets: ['latin'] }) export default function OGLayout({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` Advanced: Template Variants [#advanced-template-variants] Support different OG image styles for different content types: ```typescript // app/api/og/route.ts export async function GET(request: NextRequest) { const variant = request.nextUrl.searchParams.get('variant') ?? 'default' const title = request.nextUrl.searchParams.get('title') ?? '' const templatePath = variant === 'blog' ? '/og-template/blog' : '/og-template' const templateUrl = `${APP_URL}${templatePath}?title=${encodeURIComponent(title)}` // ... rest of screenshot logic } ``` Debugging [#debugging] To preview your OG images locally: 1. Start your dev server: `bun dev` 2. Visit the template directly: `http://localhost:3000/og-template?title=Hello+World` 3. Test the API route: `http://localhost:3000/api/og?title=Hello+World` 4. Validate with social platform debuggers: * [Twitter Card Validator](https://cards-dev.twitter.com/validator) * [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) * [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/) --- # Full-Page Captures (/docs/guides/pdf-screenshots) Overview [#overview] Full-page screenshots capture the **entire scrollable content** of a web page as a single, tall image. This is useful for archiving pages, generating PDFs, visual regression testing, and creating long-form page previews. When `fullPage=true` is set, ScreenshotAPI scrolls the entire page and stitches the result into one image. The `height` parameter is ignored — the screenshot height matches the page's full scroll height. Basic Full-Page Capture [#basic-full-page-capture] ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&fullPage=true" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output fullpage.png ``` ```typescript const params = new URLSearchParams({ url: 'https://example.com', fullPage: 'true' }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } } ) const buffer = Buffer.from(await response.arrayBuffer()) await fs.promises.writeFile('fullpage.png', buffer) ``` ```python import requests response = requests.get( "https://screenshotapi.to/api/v1/screenshot", params={"url": "https://example.com", "fullPage": "true"}, headers={"x-api-key": os.environ["SCREENSHOTAPI_KEY"]} ) with open("fullpage.png", "wb") as f: f.write(response.content) ``` ```go req, _ := http.NewRequest("GET", "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&fullPage=true", nil) req.Header.Set("x-api-key", os.Getenv("SCREENSHOTAPI_KEY")) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() file, _ := os.Create("fullpage.png") defer file.Close() io.Copy(file, resp.Body) ``` ```ruby require "net/http" require "uri" uri = URI("https://screenshotapi.to/api/v1/screenshot?url=https://example.com&fullPage=true") req = Net::HTTP::Get.new(uri) req["x-api-key"] = ENV["SCREENSHOTAPI_KEY"] response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } File.binwrite("fullpage.png", response.body) ``` ```php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&fullPage=true", CURLOPT_HTTPHEADER => ["x-api-key: " . getenv("SCREENSHOTAPI_KEY")], CURLOPT_RETURNTRANSFER => true, ]); $image = curl_exec($ch); curl_close($ch); file_put_contents("fullpage.png", $image); ``` File Size Optimization [#file-size-optimization] Full-page screenshots can produce large files, especially for long pages. Use these strategies to reduce file size: Use WebP format [#use-webp-format] WebP typically produces 30–50% smaller files than PNG with minimal quality loss: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&fullPage=true&type=webp&quality=85" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output fullpage.webp ``` Use JPEG for photographic content [#use-jpeg-for-photographic-content] If the page is mostly photos (not text/UI), JPEG can be even smaller: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&fullPage=true&type=jpeg&quality=80" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output fullpage.jpg ``` Reduce viewport width [#reduce-viewport-width] A narrower viewport produces a proportionally smaller image: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&fullPage=true&width=800&type=webp&quality=85" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output fullpage-narrow.webp ``` Handling Lazy-Loaded Content [#handling-lazy-loaded-content] Many modern websites lazy-load images and content as the user scrolls. ScreenshotAPI handles this automatically in full-page mode by scrolling through the page before capturing. However, some sites may need additional waiting: Wait for network idle [#wait-for-network-idle] ```bash curl "https://screenshotapi.to/api/v1/screenshot?\ url=https://example.com&\ fullPage=true&\ waitUntil=networkidle0" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output fullpage.png ``` Add a delay for animations [#add-a-delay-for-animations] ```bash curl "https://screenshotapi.to/api/v1/screenshot?\ url=https://example.com&\ fullPage=true&\ waitUntil=networkidle2&\ delay=2000" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output fullpage.png ``` Common Use Cases [#common-use-cases] Web Page Archival [#web-page-archival] Archive a full page for compliance or records: ```typescript async function archivePage(url: string, archiveDir: string) { const params = new URLSearchParams({ url, fullPage: 'true', type: 'png', width: '1440' }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } } ) const buffer = Buffer.from(await response.arrayBuffer()) const timestamp = new Date().toISOString().replace(/[:.]/g, '-') const filename = `${archiveDir}/archive-${timestamp}.png` await fs.promises.writeFile(filename, buffer) return filename } ``` Visual Regression Testing [#visual-regression-testing] Capture full-page screenshots for comparison in CI: ```typescript import { existsSync } from 'node:fs' import { readFile, writeFile } from 'node:fs/promises' async function captureBaseline(url: string, name: string) { const params = new URLSearchParams({ url, fullPage: 'true', width: '1440', type: 'png' }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } } ) const buffer = Buffer.from(await response.arrayBuffer()) await writeFile(`__screenshots__/${name}.png`, buffer) } // In your CI pipeline: // 1. Capture screenshots after deploy // 2. Compare with baseline using pixelmatch or similar // 3. Flag visual regressions for review ``` PDF Export [#pdf-export] ScreenshotAPI supports native PDF export via the `type=pdf` parameter. This produces a real PDF document (not a screenshot wrapped in a PDF) with selectable text, working links, and proper page layout: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&type=pdf" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output page.pdf ``` ```typescript const params = new URLSearchParams({ url: 'https://example.com', type: 'pdf' }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } } ) const buffer = Buffer.from(await response.arrayBuffer()) await fs.promises.writeFile('page.pdf', buffer) ``` PDF export uses 1 credit per request, just like image screenshots. The `fullPage`, `width`, and `delay` parameters work with PDF output. The `quality` parameter is ignored for PDFs. Limitations [#limitations] Full-page screenshots have a maximum height of **10,000 pixels**. Pages taller than this will be truncated. For extremely long pages, consider capturing specific sections using viewport-based screenshots with scroll offsets. * **Sticky/fixed elements** — Headers and footers with `position: fixed` will appear once at their natural position, not repeated throughout the image. * **Infinite scroll pages** — Pages that load content endlessly (like social media feeds) will capture whatever has loaded at the time of capture. Add a `delay` to allow more content to load. * **Very tall pages** — Pages with thousands of pixels of scroll height may take longer to capture and produce very large files. --- # Vercel / Next.js Integration (/docs/guides/vercel-integration) Overview [#overview] This guide shows how to integrate ScreenshotAPI into a Next.js application deployed on Vercel. You'll create an API route that proxies screenshot requests, keeping your API key secure on the server. Setup [#setup] Add your API key to environment variables [#add-your-api-key-to-environment-variables] In your Vercel project settings or `.env.local`: ```bash SCREENSHOTAPI_KEY=sk_live_your_key_here ``` Create the API route [#create-the-api-route] Create `app/api/screenshot/route.ts`: ```typescript import { NextResponse, type NextRequest } from 'next/server' const SCREENSHOTAPI_KEY = process.env.SCREENSHOTAPI_KEY! const BASE_URL = 'https://screenshotapi.to' export async function GET(request: NextRequest) { const url = request.nextUrl.searchParams.get('url') if (!url) { return NextResponse.json({ error: 'url is required' }, { status: 400 }) } const params = new URLSearchParams({ url }) // Forward optional parameters const optionalParams = [ 'width', 'height', 'fullPage', 'type', 'quality', 'colorScheme', 'waitUntil', 'waitForSelector', 'delay' ] for (const param of optionalParams) { const value = request.nextUrl.searchParams.get(param) if (value) params.set(param, value) } try { const response = await fetch( `${BASE_URL}/api/v1/screenshot?${params}`, { headers: { 'x-api-key': SCREENSHOTAPI_KEY } } ) if (!response.ok) { const error = await response.json() return NextResponse.json(error, { status: response.status }) } const buffer = await response.arrayBuffer() return new NextResponse(buffer, { headers: { 'Content-Type': response.headers.get('content-type') ?? 'image/png', 'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400', 'x-credits-remaining': response.headers.get('x-credits-remaining') ?? '', 'x-duration-ms': response.headers.get('x-duration-ms') ?? '' } }) } catch { return NextResponse.json({ error: 'Screenshot failed' }, { status: 500 }) } } ``` Use from your frontend [#use-from-your-frontend] ```typescript function ScreenshotImage({ url }: { url: string }) { const src = `/api/screenshot?url=${encodeURIComponent(url)}&type=webp&quality=80` return ( {`Screenshot ) } ``` Server Component Usage [#server-component-usage] Fetch screenshots directly in React Server Components: ```typescript async function ServerScreenshot({ url }: { url: string }) { const params = new URLSearchParams({ url, type: 'webp', quality: '80' }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! }, next: { revalidate: 3600 } } ) if (!response.ok) { return
Failed to load screenshot
} const buffer = await response.arrayBuffer() const base64 = Buffer.from(buffer).toString('base64') const contentType = response.headers.get('content-type') ?? 'image/webp' const dataUri = `data:${contentType};base64,${base64}` return ( {`Screenshot ) } ``` Using `next: { revalidate: 3600 }` caches the screenshot for 1 hour with Next.js ISR, reducing API calls and credit usage. Caching Strategies [#caching-strategies] Edge Caching with Vercel [#edge-caching-with-vercel] Set appropriate cache headers to leverage Vercel's edge network: ```typescript return new NextResponse(buffer, { headers: { 'Content-Type': contentType, // Cache for 1 hour, serve stale for 24 hours while revalidating 'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400', // Vary by query params so different URLs get different cache entries 'Vary': 'Accept' } }) ``` In-Memory Cache with Map [#in-memory-cache-with-map] For high-frequency screenshots of the same URLs: ```typescript const cache = new Map() const CACHE_TTL = 60 * 60 * 1000 // 1 hour async function cachedScreenshot(url: string): Promise<{ buffer: ArrayBuffer; contentType: string }> { const cached = cache.get(url) if (cached && Date.now() - cached.timestamp < CACHE_TTL) { return { buffer: cached.buffer, contentType: cached.contentType } } const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?url=${encodeURIComponent(url)}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } } ) const buffer = await response.arrayBuffer() const contentType = response.headers.get('content-type') ?? 'image/png' cache.set(url, { buffer, contentType, timestamp: Date.now() }) return { buffer, contentType } } ``` Vercel Function Configuration [#vercel-function-configuration] For long pages or slow-loading sites, increase the function timeout: ```typescript // app/api/screenshot/route.ts export const maxDuration = 30 // seconds (available on Pro plan) ``` Vercel Hobby plans have a 10-second function timeout. If your screenshots take longer (full-page captures of large sites), consider upgrading to Pro for the 60-second limit. Full Example: Link Preview Component [#full-example-link-preview-component] A complete link preview component that generates and caches thumbnails: ```typescript import { Suspense } from 'react' function LinkPreviewSkeleton() { return (
) } async function LinkPreviewImage({ url }: { url: string }) { const params = new URLSearchParams({ url, width: '800', height: '600', type: 'webp', quality: '75' }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! }, next: { revalidate: 86400 } } ) if (!response.ok) return null const buffer = Buffer.from(await response.arrayBuffer()) const base64 = buffer.toString('base64') const src = `data:image/webp;base64,${base64}` return ( ) } export function LinkPreview({ url, title }: { url: string; title: string }) { return (
}>

{title}

{url}

) } ``` --- # Automation & No-Code (/docs/integrations/automation) Overview [#overview] ScreenshotAPI works with any automation platform that can make HTTP requests. No SDKs or code needed — configure the API URL, pass your API key as a header, and start capturing screenshots in your workflows. How It Works [#how-it-works] Every automation platform integration follows the same pattern: 1. **Trigger** — A form submission, schedule, webhook, or event starts the workflow. 2. **HTTP Request** — The platform makes a `GET` request to the ScreenshotAPI endpoint with your URL and parameters. 3. **Process** — Store the screenshot in cloud storage, send via email/Slack, or pass to the next step. The request always looks like this: ``` GET https://screenshotapi.to/api/v1/screenshot?url=https://example.com&width=1440&height=900&type=png Header: x-api-key: sk_live_your_key_here ``` Zapier [#zapier] Use the **Webhooks by Zapier** action (Custom Request) to call ScreenshotAPI. Set the method to `GET`, add your API key as an `x-api-key` header, and map query parameters from your trigger data. Chain with Google Drive, Slack, email, or any Zapier action to store or share results. Full walkthrough: [Zapier integration guide](/integrations/zapier) n8n [#n8n] Use the **HTTP Request** node with `GET` method. n8n supports binary data handling, so you can pipe the screenshot response directly into file storage or messaging nodes. Self-hosted n8n gives you unlimited executions. Full walkthrough: [n8n integration guide](/integrations/n8n) Make (Integromat) [#make-integromat] Use the **HTTP > Make a request** module. Configure the URL, method, and headers. Make's binary handling lets you route screenshots to Google Drive, Dropbox, Airtable, or any connected app. Full walkthrough: [Make integration guide](/integrations/make) Retool [#retool] Use a **REST API resource** pointed at the ScreenshotAPI endpoint. Build internal tools with screenshot previews, bulk capture workflows, and admin dashboards. Retool Workflows support scheduled screenshot jobs. Full walkthrough: [Retool integration guide](/integrations/retool) Each screenshot uses **1 credit**. A workflow that captures 5 pages daily uses \~150 credits/month. Check the [pricing page](/pricing) for credit packs. Further Reading [#further-reading] * [Screenshot API reference](/docs/api/screenshot) — all available parameters * [cURL reference](/docs/sdks/curl) — test API calls from the command line before building workflows * [Authentication](/docs/authentication) — API key management --- # Backend Frameworks (/docs/integrations/backend-frameworks) Overview [#overview] Backend integrations call ScreenshotAPI directly from your server. The API key stays safe, and you have full control over caching, storage, and background processing. Every backend framework follows the same pattern: make an HTTP request to the screenshot endpoint, receive image bytes, and return or store them. Python Frameworks [#python-frameworks] Django [#django] Create views or DRF endpoints that proxy screenshot requests. Use Celery for background processing and Django's storage backends for persistence. ```python import requests from django.conf import settings from django.http import HttpResponse def screenshot_view(request): url = request.GET.get('url') response = requests.get( 'https://screenshotapi.to/api/v1/screenshot', params={'url': url, 'width': 1440, 'height': 900, 'type': 'png'}, headers={'x-api-key': settings.SCREENSHOTAPI_KEY}, timeout=30, ) return HttpResponse(response.content, content_type='image/png') ``` Full walkthrough: [Django integration guide](/integrations/django) Flask [#flask] Lightweight screenshot endpoints with Flask's routing and optional caching. Full walkthrough: [Flask integration guide](/integrations/flask) FastAPI [#fastapi] Async screenshot capture with Pydantic validation and background tasks. Full walkthrough: [FastAPI integration guide](/integrations/fastapi) **Python SDK:** [Python SDK reference](/docs/sdks/python) Ruby Frameworks [#ruby-frameworks] Rails [#rails] Controllers, Active Storage integration, and Sidekiq background jobs for screenshot processing. Full walkthrough: [Rails integration guide](/integrations/rails) Sinatra [#sinatra] Minimal Ruby screenshot endpoints for lightweight applications. Full walkthrough: [Sinatra integration guide](/integrations/sinatra) **Ruby SDK:** [Ruby SDK reference](/docs/sdks/ruby) PHP Frameworks [#php-frameworks] Laravel [#laravel] Controllers, queued jobs with Laravel Horizon, and Storage facade for saving screenshots. Full walkthrough: [Laravel integration guide](/integrations/laravel) WordPress [#wordpress] Shortcodes, REST API endpoints, and WP-Cron jobs for scheduled captures. Full walkthrough: [WordPress integration guide](/integrations/wordpress) **PHP SDK:** [PHP SDK reference](/docs/sdks/php) JavaScript / TypeScript Frameworks [#javascript--typescript-frameworks] Express [#express] Middleware-based proxy routes with streaming support and response caching. ```typescript import express from 'express' const app = express() app.get('/api/screenshot', async (req, res) => { const { url, width = '1440', height = '900', type = 'png' } = req.query const params = new URLSearchParams({ url: String(url), width: String(width), height: String(height), type: String(type), }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } } ) const buffer = Buffer.from(await response.arrayBuffer()) res.set('Content-Type', `image/${type}`) res.set('Cache-Control', 'public, max-age=86400') res.send(buffer) }) ``` Full walkthrough: [Express integration guide](/integrations/express) Deno [#deno] Deno.serve, Fresh routes, and Deno Deploy. Uses the built-in `fetch` API with no extra dependencies. Full walkthrough: [Deno integration guide](/integrations/deno) **JavaScript SDK:** [JavaScript / TypeScript SDK reference](/docs/sdks/javascript) Go Frameworks [#go-frameworks] Gin [#gin] Go handlers with goroutines for concurrent screenshot captures. Full walkthrough: [Gin integration guide](/integrations/gin) **Go SDK:** [Go SDK reference](/docs/sdks/go) Java Frameworks [#java-frameworks] Spring Boot [#spring-boot] RestTemplate/WebClient for HTTP calls and `@Scheduled` tasks for automated captures. Full walkthrough: [Spring Boot integration guide](/integrations/spring-boot) **API access:** Use the [Screenshot API](/docs/api/screenshot) directly with any Java HTTP client, or use [cURL](/docs/sdks/curl) for quick testing. .NET [#net] HttpClient, ASP.NET controller endpoints, and hosted background services for batch processing. Full walkthrough: [.NET integration guide](/integrations/dotnet) **API access:** Use the [Screenshot API](/docs/api/screenshot) directly with `HttpClient`, or use [cURL](/docs/sdks/curl) for quick testing. Further Reading [#further-reading] * [Screenshot API reference](/docs/api/screenshot) — all available parameters * [Authentication](/docs/authentication) — API key management * [Credits](/docs/credits) — pricing and credit packs --- # CI/CD Integrations (/docs/integrations/ci-cd) Overview [#overview] ScreenshotAPI integrates into any CI/CD pipeline that can run `curl` or a script. Common use cases include visual regression testing on pull requests, capturing deployment screenshots for audit trails, and monitoring production pages on a schedule. Since ScreenshotAPI handles browser rendering remotely, your CI runners don't need Chromium, Playwright, or any browser dependencies — saving minutes of build time and hundreds of megabytes of disk space. General Pattern [#general-pattern] Every CI/CD integration follows this flow: 1. Store your API key as a **CI secret** (never hardcode it in pipeline files). 2. Run a `curl` command or script that calls the ScreenshotAPI endpoint. 3. Save the screenshot as a **build artifact** or upload to storage. 4. Optionally compare against baseline images for visual regression. ```bash curl -s -o screenshot.png \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&width=1440&height=900&type=png" ``` GitHub Actions [#github-actions] Add your API key as a repository secret, then use `curl` in workflow steps. Supports matrix strategies for multi-viewport captures, artifact uploads, and PR comment previews. ```yaml - name: Capture screenshot run: | curl -s -o screenshot.png \ -H "x-api-key: ${{ secrets.SCREENSHOTAPI_KEY }}" \ "https://screenshotapi.to/api/v1/screenshot?url=https://yoursite.com&width=1440&height=900&type=png" ``` Full walkthrough: [GitHub Actions integration guide](/integrations/github-actions) GitLab CI [#gitlab-ci] Store the API key as a CI/CD variable, then call the API in pipeline jobs. Screenshots can be saved as GitLab artifacts with configurable retention. Full walkthrough: [GitLab CI integration guide](/integrations/gitlab-ci) Bitbucket Pipelines [#bitbucket-pipelines] Add the API key as a repository variable, use `curl` in pipeline steps, and store screenshots as Bitbucket artifacts. Full walkthrough: [Bitbucket Pipelines integration guide](/integrations/bitbucket-pipelines) Visual Regression Testing [#visual-regression-testing] A common CI workflow captures screenshots on every PR and compares them against baseline images: 1. **Capture** — Take screenshots of key pages on the PR's preview deployment. 2. **Compare** — Diff current screenshots against committed baselines (pixel comparison or perceptual hash). 3. **Report** — Fail the build or post a comment if visual changes exceed a threshold. 4. **Update** — If the changes are intentional, commit new baselines. Store baseline images in your repository so they version alongside your code. CI artifacts are ephemeral — baselines need to persist across runs. Further Reading [#further-reading] * [cURL reference](/docs/sdks/curl) — the foundation for all CI integrations * [JavaScript SDK](/docs/sdks/javascript) — for more complex capture scripts * [Screenshot API reference](/docs/api/screenshot) — all available parameters --- # Frontend Frameworks (/docs/integrations/frontend-frameworks) Overview [#overview] Frontend frameworks cannot call ScreenshotAPI directly from the browser — your API key must stay on the server. The standard pattern is a lightweight backend proxy that accepts requests from your frontend and forwards them to the ScreenshotAPI endpoint. Never expose your `SCREENSHOTAPI_KEY` in client-side code. Always proxy through a server endpoint. The Proxy Pattern [#the-proxy-pattern] Every frontend integration follows the same structure: 1. Your frontend component renders an `` tag (or fetches a blob) from your own API route. 2. Your server-side route receives the request, calls ScreenshotAPI with the API key, and streams the image back. 3. Cache headers on your proxy route let the browser and CDN avoid repeat requests. ``` Browser → Your API Route → ScreenshotAPI → Image returned → Cached & displayed ``` React [#react] Use the [JavaScript SDK](/docs/sdks/javascript) in your backend, then fetch from React components. Supports Vite, CRA, Remix, and any React setup. ```tsx function WebsiteThumbnail({ url }: { url: string }) { const src = `/api/screenshot?url=${encodeURIComponent(url)}&type=webp` return {`Screenshot } ``` For advanced patterns with TanStack Query, loading states, and galleries, see the full [React integration guide](/integrations/react). Next.js [#nextjs] Next.js has first-class support via API routes and React Server Components. Screenshots can be fetched server-side with ISR caching. ```typescript const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?url=${encodeURIComponent(url)}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! }, next: { revalidate: 3600 } } ) ``` Full walkthrough: [Next.js / Vercel integration guide](/integrations/nextjs) and the [Vercel deployment guide](/docs/guides/vercel-integration). Vue [#vue] Vue 3 and Nuxt apps follow the same proxy pattern. Use `useFetch` or `useAsyncData` in Nuxt for server-side data fetching, or a standard `fetch` call in a Vue SFC. ```vue ``` Full walkthrough: [Vue integration guide](/integrations/vue) Angular [#angular] Use Angular's `HttpClient` to fetch screenshots as blobs from your proxy endpoint. Full walkthrough: [Angular integration guide](/integrations/angular) Svelte / SvelteKit [#svelte--sveltekit] SvelteKit server routes make excellent screenshot proxies. Use `+server.ts` endpoints to call ScreenshotAPI. Full walkthrough: [Svelte integration guide](/integrations/svelte) Remix [#remix] Remix resource routes (`loader` functions returning `Response` objects) work perfectly as screenshot proxies. Full walkthrough: [Remix integration guide](/integrations/remix) Astro [#astro] Astro API endpoints (`src/pages/api/*.ts`) can proxy screenshot requests in both SSR and hybrid mode. Full walkthrough: [Astro integration guide](/integrations/astro) Further Reading [#further-reading] * [JavaScript / TypeScript SDK](/docs/sdks/javascript) — full client reference * [Screenshot API reference](/docs/api/screenshot) — all available parameters * [Generating OG Images](/docs/guides/og-images) — dynamic social images with ScreenshotAPI --- # Integrations Overview (/docs/integrations) Works With Your Stack [#works-with-your-stack] ScreenshotAPI is a simple REST API, so it works everywhere HTTP requests do. We provide [official SDKs](/docs/sdks/javascript) for the most popular languages and detailed integration guides for 30+ frameworks, platforms, and automation tools. Browse the categories below to find your stack, or jump straight to the [SDK docs](/docs/sdks/javascript) if you just need the API client. Frontend Frameworks [#frontend-frameworks] Build screenshot features into your client-side apps. All frontend integrations use a backend proxy to keep your API key secure. **Relevant SDK:** [JavaScript / TypeScript SDK](/docs/sdks/javascript) Backend Frameworks [#backend-frameworks] Integrate screenshots into your server-side applications. Each guide includes production-ready code with error handling, caching, and background processing patterns. **Relevant SDKs:** [Python](/docs/sdks/python) · [Ruby](/docs/sdks/ruby) · [PHP](/docs/sdks/php) · [Go](/docs/sdks/go) · [JavaScript](/docs/sdks/javascript) Serverless & Hosting Platforms [#serverless--hosting-platforms] Deploy screenshot functionality on serverless platforms and edge networks with zero infrastructure to manage. **Relevant SDKs:** [JavaScript](/docs/sdks/javascript) · [Python](/docs/sdks/python) · [cURL](/docs/sdks/curl) Automation & No-Code [#automation--no-code] Capture screenshots without writing code using popular automation platforms. **Relevant docs:** [Screenshot API Reference](/docs/api/screenshot) · [cURL examples](/docs/sdks/curl) CI/CD [#cicd] Automate screenshots in your continuous integration pipelines for visual regression testing and deployment monitoring. **Relevant docs:** [cURL](/docs/sdks/curl) · [JavaScript SDK](/docs/sdks/javascript) Using the REST API Directly [#using-the-rest-api-directly] Every integration above ultimately calls the same REST API. If your stack isn't listed, you can use ScreenshotAPI from any language or tool that can make HTTP requests: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com" \ -H "x-api-key: sk_live_your_key_here" \ --output screenshot.png ``` See the [Screenshot API reference](/docs/api/screenshot) for the complete parameter list, or pick an [SDK](/docs/sdks/javascript) for typed, ergonomic access. --- # Serverless & Hosting Platforms (/docs/integrations/serverless-platforms) Overview [#overview] ScreenshotAPI is a perfect fit for serverless deployments. Traditional screenshot solutions require a Chromium binary (50–100 MB), long cold starts, and high memory. ScreenshotAPI eliminates all of that — your function makes one HTTP request and receives the image bytes. | Approach | Cold Start | Package Size | Memory Needed | | --------------------------- | ---------- | ------------ | ------------- | | ScreenshotAPI | \~50 ms | \< 1 MB | 128–256 MB | | Puppeteer/chrome-aws-lambda | 3–8 s | \~50 MB | 1600+ MB | | Playwright | 5–12 s | \~80 MB | 1600+ MB | Vercel [#vercel] Serverless and edge functions with ISR caching. The [Vercel / Next.js guide](/docs/guides/vercel-integration) covers API routes, Server Components, and edge caching in depth. Full walkthrough: [Vercel integration guide](/integrations/vercel) AWS Lambda [#aws-lambda] Node.js and Python handlers, API Gateway, S3 storage, SQS batch processing, and CDK infrastructure-as-code. No Lambda layers or Chromium binaries needed. ```typescript export async function handler(event: { queryStringParameters?: Record }) { const url = event.queryStringParameters?.url const params = new URLSearchParams({ url: url!, width: '1440', height: '900', type: 'png' }) const response = await fetch( `https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } } ) const buffer = Buffer.from(await response.arrayBuffer()) return { statusCode: 200, headers: { 'Content-Type': 'image/png' }, body: buffer.toString('base64'), isBase64Encoded: true, } } ``` Full walkthrough: [AWS Lambda integration guide](/integrations/aws-lambda) Cloudflare Workers [#cloudflare-workers] Edge screenshot capture with R2 storage and global distribution. Workers have a lightweight runtime that pairs well with ScreenshotAPI's HTTP-only approach. Full walkthrough: [Cloudflare Workers integration guide](/integrations/cloudflare-workers) Google Cloud Functions [#google-cloud-functions] HTTP-triggered functions with Cloud Storage for persistence and Pub/Sub for event-driven workflows. Full walkthrough: [Google Cloud Functions integration guide](/integrations/google-cloud-functions) Azure Functions [#azure-functions] HTTP triggers, Blob Storage for screenshot persistence, and Queue triggers for batch processing. Full walkthrough: [Azure Functions integration guide](/integrations/azure-functions) Netlify [#netlify] Netlify Functions (AWS Lambda under the hood) and edge handlers for screenshot endpoints deployed alongside your Netlify site. Full walkthrough: [Netlify integration guide](/integrations/netlify) Supabase Edge Functions [#supabase-edge-functions] Deno-based edge functions with Supabase Storage integration for persistent screenshot archives. Full walkthrough: [Supabase Edge Functions integration guide](/integrations/supabase-edge-functions) Docker [#docker] Containerize a screenshot service in any language. Since ScreenshotAPI handles browser rendering remotely, your Docker image stays small — no Chromium, no system dependencies. Full walkthrough: [Docker integration guide](/integrations/docker) All serverless guides use the same [Screenshot API](/docs/api/screenshot). The only difference is how you store credentials and return the response in each platform's conventions. Further Reading [#further-reading] * [JavaScript / TypeScript SDK](/docs/sdks/javascript) * [Python SDK](/docs/sdks/python) * [cURL reference](/docs/sdks/curl) * [Screenshot API reference](/docs/api/screenshot) --- # CLI (/docs/sdks/cli) The ScreenshotAPI CLI is the fastest way to use ScreenshotAPI from local scripts, CI jobs, and AI coding agents. It saves screenshots and PDFs as files by default, prints compact metadata, and does not require Chromium, Playwright, or Puppeteer. Installation [#installation] ```bash npm install -g @screenshotapi/cli ``` Authenticate once: ```bash screenshotapi auth login --key sk_live_your_key_here ``` For CI and agent environments, prefer an environment variable: ```bash export SCREENSHOTAPI_KEY=sk_live_your_key_here ``` Never commit API keys. Store `SCREENSHOTAPI_KEY` in your CI secret manager or local shell environment. Quick Start [#quick-start] ```bash # Capture a URL as PNG screenshotapi capture https://example.com --out screenshot.png # Full-page Retina screenshot with cleanup screenshotapi capture https://example.com \ --full-page \ --device-pixel-ratio 2 \ --block-ads \ --remove-cookie-banners \ --out homepage.png # Render HTML to an image screenshotapi html --html-file ./page.html --out page.png # Generate a PDF screenshotapi pdf https://example.com --out page.pdf # Fetch page metadata without spending screenshot credits screenshotapi metadata https://example.com --output json ``` Agent Skill [#agent-skill] Install the bundled agent skill so your AI assistant knows the current command surface and best practices: ```bash npx skills add miketromba/screenshot-saas --skill screenshotapi ``` The skill delegates to: ```bash screenshotapi learn ``` That keeps agent instructions tied to the installed CLI version. Output Modes [#output-modes] | Flag | Output | Best for | | ------------------ | --------------------- | --------------------- | | `--output compact` | One `key=value` line | AI agents and logs | | `--output pretty` | Human-readable labels | Interactive terminals | | `--output json` | Minified JSON | Scripts and tests | | `--output path` | Saved file path only | Shell pipelines | Non-interactive runs default to compact output: ```text capture path=homepage.png url=https://example.com type=png bytes=184203 contentType=image/png screenshotId=scr_abc durationMs=1934 cache=MISS usageSource=plan creditsRemaining=199 ``` Common Options [#common-options] | Flag | Description | | ---------------------------------------- | ------------------------------------------------------------------------------ | | `--out ` | Save to a deterministic path. | | `--type ` | Output type for `capture` and `html`. | | `--width ` / `--height ` | Viewport size. | | `--full-page` | Capture the full scrollable page. | | `--quality <1-100>` | JPEG/WebP quality. | | `--wait-until ` | `load`, `domcontentloaded`, `networkidle0`, or `networkidle2`. | | `--wait-for-selector ` | Wait for a selector before capture. | | `--delay ` | Extra wait after load. | | `--block-ads` | Block common ad network requests. | | `--remove-cookie-banners` | Remove common cookie banners. | | `--remove-popups` | Remove common modal overlays. | | `--remove-elements ` | Remove specific selectors. | | `--css-file ` / `--js-file ` | Inject CSS or JavaScript from files. | | `--cache-ttl ` | `0` disables cache; positive values cache identical captures for up to 7 days. | CI Example [#ci-example] ```yaml - name: Capture production homepage env: SCREENSHOTAPI_KEY: ${{ secrets.SCREENSHOTAPI_KEY }} run: | npx @screenshotapi/cli capture https://example.com \ --full-page \ --out artifacts/homepage.png \ --output json ``` Further Reading [#further-reading] * [Screenshot API reference](/docs/api/screenshot) * [Authentication](/docs/authentication) * [CI/CD integrations](/docs/integrations/ci-cd) --- # C# / .NET (/docs/sdks/csharp) There's no official .NET SDK yet. The examples below call the [REST API](/docs/api/screenshot) directly with the built-in `HttpClient` — no dependencies, copy-paste ready. Prefer a native client? Official SDKs ship for [JavaScript](/docs/sdks/javascript), [Python](/docs/sdks/python), [Go](/docs/sdks/go), [Ruby](/docs/sdks/ruby), and [PHP](/docs/sdks/php). Overview [#overview] These examples use the built-in `HttpClient` from .NET 6+. No external packages are required. Quick Start [#quick-start] Set your API key [#set-your-api-key] ```bash export SCREENSHOTAPI_KEY="sk_live_your_key_here" ``` Take a screenshot [#take-a-screenshot] ```csharp using System.Net.Http.Headers; using System.Web; var apiKey = Environment.GetEnvironmentVariable("SCREENSHOTAPI_KEY")!; using var client = new HttpClient(); client.DefaultRequestHeaders.Add("x-api-key", apiKey); var query = HttpUtility.ParseQueryString(string.Empty); query["url"] = "https://example.com"; var response = await client.GetAsync( $"https://screenshotapi.to/api/v1/screenshot?{query}"); response.EnsureSuccessStatusCode(); var bytes = await response.Content.ReadAsByteArrayAsync(); await File.WriteAllBytesAsync("screenshot.png", bytes); Console.WriteLine($"Credits remaining: {response.Headers.GetValues("x-credits-remaining").First()}"); ``` Client Class [#client-class] A reusable client with full options support: ```csharp using System.Net.Http.Headers; using System.Web; public record ScreenshotResult( byte[] Content, string ContentType, int CreditsRemaining, string ScreenshotId, int DurationMs ); public record ScreenshotOptions { public required string Url { get; init; } public int? Width { get; init; } public int? Height { get; init; } public bool FullPage { get; init; } public string Format { get; init; } = "png"; public int? Quality { get; init; } public string? ColorScheme { get; init; } public string? WaitUntil { get; init; } public string? WaitForSelector { get; init; } public int? Delay { get; init; } } public class ScreenshotAPI : IDisposable { private readonly HttpClient _client; private readonly string _baseUrl; public ScreenshotAPI(string apiKey, string baseUrl = "https://screenshotapi.to") { _baseUrl = baseUrl; _client = new HttpClient(); _client.DefaultRequestHeaders.Add("x-api-key", apiKey); } public async Task CaptureAsync(ScreenshotOptions options) { var query = HttpUtility.ParseQueryString(string.Empty); query["url"] = options.Url; if (options.Width.HasValue) query["width"] = options.Width.Value.ToString(); if (options.Height.HasValue) query["height"] = options.Height.Value.ToString(); if (options.FullPage) query["fullPage"] = "true"; if (options.Format != "png") query["type"] = options.Format; if (options.Quality.HasValue) query["quality"] = options.Quality.Value.ToString(); if (options.ColorScheme is not null) query["colorScheme"] = options.ColorScheme; if (options.WaitUntil is not null) query["waitUntil"] = options.WaitUntil; if (options.WaitForSelector is not null) query["waitForSelector"] = options.WaitForSelector; if (options.Delay.HasValue) query["delay"] = options.Delay.Value.ToString(); var response = await _client.GetAsync($"{_baseUrl}/api/v1/screenshot?{query}"); if (!response.IsSuccessStatusCode) { var error = await response.Content.ReadAsStringAsync(); throw new HttpRequestException( $"Screenshot failed ({(int)response.StatusCode}): {error}"); } int GetHeader(string name) => int.TryParse( response.Headers.TryGetValues(name, out var vals) ? vals.First() : "0", out var v) ? v : 0; string GetHeaderStr(string name) => response.Headers.TryGetValues(name, out var vals) ? vals.First() : ""; return new ScreenshotResult( Content: await response.Content.ReadAsByteArrayAsync(), ContentType: response.Content.Headers.ContentType?.MediaType ?? "image/png", CreditsRemaining: GetHeader("x-credits-remaining"), ScreenshotId: GetHeaderStr("x-screenshot-id"), DurationMs: GetHeader("x-duration-ms") ); } public void Dispose() => _client.Dispose(); } ``` Usage [#usage] ```csharp using var api = new ScreenshotAPI(Environment.GetEnvironmentVariable("SCREENSHOTAPI_KEY")!); var result = await api.CaptureAsync(new ScreenshotOptions { Url = "https://github.com", Width = 1280, Height = 720, Format = "webp", Quality = 85 }); await File.WriteAllBytesAsync("github.webp", result.Content); Console.WriteLine($"Credits remaining: {result.CreditsRemaining}"); Console.WriteLine($"Duration: {result.DurationMs}ms"); ``` Common Patterns [#common-patterns] Batch Screenshots [#batch-screenshots] Capture multiple URLs concurrently: ```csharp var urls = new[] { "https://example.com", "https://github.com", "https://news.ycombinator.com" }; var tasks = urls.Select(async (url, i) => { try { var result = await api.CaptureAsync(new ScreenshotOptions { Url = url }); await File.WriteAllBytesAsync($"screenshot-{i}.png", result.Content); Console.WriteLine($"✓ {url}"); } catch (Exception ex) { Console.Error.WriteLine($"✗ {url}: {ex.Message}"); } }); await Task.WhenAll(tasks); ``` ASP.NET Minimal API [#aspnet-minimal-api] Serve screenshots in an ASP.NET application: ```csharp var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); using var screenshotApi = new ScreenshotAPI( Environment.GetEnvironmentVariable("SCREENSHOTAPI_KEY")!); app.MapGet("/screenshot", async (string url) => { try { var result = await screenshotApi.CaptureAsync(new ScreenshotOptions { Url = url, Format = "webp", Quality = 80 }); return Results.File(result.Content, result.ContentType); } catch { return Results.Problem("Screenshot failed", statusCode: 502); } }); app.Run(); ``` ASP.NET Controller [#aspnet-controller] ```csharp using Microsoft.AspNetCore.Mvc; [ApiController] [Route("[controller]")] public class ScreenshotController : ControllerBase { private readonly ScreenshotAPI _api; public ScreenshotController() { _api = new ScreenshotAPI(Environment.GetEnvironmentVariable("SCREENSHOTAPI_KEY")!); } [HttpGet] public async Task Get([FromQuery] string url) { if (string.IsNullOrEmpty(url)) return BadRequest(new { error = "url is required" }); try { var result = await _api.CaptureAsync(new ScreenshotOptions { Url = url, Format = "webp", Quality = 80 }); Response.Headers["Cache-Control"] = "public, max-age=3600"; return File(result.Content, result.ContentType); } catch { return StatusCode(502, new { error = "Screenshot failed" }); } } } ``` Error Handling [#error-handling] ```csharp try { var result = await api.CaptureAsync(new ScreenshotOptions { Url = "https://example.com" }); } catch (HttpRequestException ex) { if (ex.Message.Contains("402")) Console.Error.WriteLine("Out of credits — purchase more at screenshotapi.to"); else if (ex.Message.Contains("403")) Console.Error.WriteLine("Invalid API key — check your configuration"); else Console.Error.WriteLine($"Screenshot failed: {ex.Message}"); } ``` --- # cURL (/docs/sdks/curl) Building in a language with an official SDK? Reach for it instead of raw HTTP: [JavaScript](/docs/sdks/javascript), [Python](/docs/sdks/python), [Go](/docs/sdks/go), [Ruby](/docs/sdks/ruby), or [PHP](/docs/sdks/php). cURL is ideal for the shell, quick tests, and CI. Overview [#overview] cURL is the simplest way to use ScreenshotAPI. Every parameter is passed as a query string, and the response is the raw image binary. Setup [#setup] Set your API key as an environment variable: ```bash export SCREENSHOTAPI_KEY="sk_live_your_key_here" ``` Basic Screenshot [#basic-screenshot] ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output screenshot.png ``` Full-Page Screenshot [#full-page-screenshot] Capture the entire scrollable page: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&fullPage=true" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output fullpage.png ``` Custom Dimensions [#custom-dimensions] Screenshot at mobile dimensions (iPhone 15 Pro): ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&width=393&height=852" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output mobile.png ``` Screenshot at tablet dimensions (iPad): ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&width=1024&height=1366" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output tablet.png ``` Image Format and Quality [#image-format-and-quality] PNG (default, lossless) [#png-default-lossless] ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&type=png" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output screenshot.png ``` JPEG (smaller file size) [#jpeg-smaller-file-size] ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&type=jpeg&quality=85" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output screenshot.jpg ``` WebP (smallest file size) [#webp-smallest-file-size] ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&type=webp&quality=80" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output screenshot.webp ``` Dark Mode [#dark-mode] Force dark mode on pages that support `prefers-color-scheme`: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&colorScheme=dark" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output dark.png ``` Waiting Strategies [#waiting-strategies] Wait for network idle [#wait-for-network-idle] ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&waitUntil=networkidle0" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output screenshot.png ``` Wait for a specific element [#wait-for-a-specific-element] ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&waitForSelector=.main-content" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output screenshot.png ``` Add a delay after page load [#add-a-delay-after-page-load] Wait 2 seconds for animations to complete: ```bash curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&delay=2000" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output screenshot.png ``` Combined Parameters [#combined-parameters] Desktop, dark mode, full-page WebP screenshot with a delay: ```bash curl "https://screenshotapi.to/api/v1/screenshot?\ url=https://github.com&\ width=1440&\ height=900&\ fullPage=true&\ type=webp&\ quality=90&\ colorScheme=dark&\ waitUntil=networkidle2&\ delay=500" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output github-dark-full.webp ``` Viewing Response Headers [#viewing-response-headers] Use `-v` (verbose) or `-D -` to see response headers including your remaining credits: ```bash curl -v "https://screenshotapi.to/api/v1/screenshot?url=https://example.com" \ -H "x-api-key: $SCREENSHOTAPI_KEY" \ --output screenshot.png 2>&1 | grep -i "x-credits\|x-screenshot\|x-duration" ``` Output: ``` < x-credits-remaining: 847 < x-screenshot-id: scr_abc123def456 < x-duration-ms: 2340 ``` Checking Your Balance [#checking-your-balance] ```bash curl "https://screenshotapi.to/api/v1/credits" \ --cookie "session=your_session_cookie" ``` Shell Script: Batch Screenshots [#shell-script-batch-screenshots] Screenshot multiple URLs from a file: ```bash #!/bin/bash API_KEY="$SCREENSHOTAPI_KEY" OUTPUT_DIR="./screenshots" mkdir -p "$OUTPUT_DIR" while IFS= read -r url; do # Create a safe filename from the URL filename=$(echo "$url" | sed 's|https\?://||; s|/|_|g; s|[^a-zA-Z0-9._-]||g') echo "Capturing: $url" curl -s "https://screenshotapi.to/api/v1/screenshot?url=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$url', safe=''))")" \ -H "x-api-key: $API_KEY" \ --output "$OUTPUT_DIR/${filename}.png" if [ $? -eq 0 ]; then echo " ✓ Saved to $OUTPUT_DIR/${filename}.png" else echo " ✗ Failed" fi done < urls.txt ``` Create a `urls.txt` file with one URL per line: ``` https://example.com https://github.com https://news.ycombinator.com ``` Then run: ```bash chmod +x batch-screenshots.sh ./batch-screenshots.sh ``` Error Handling [#error-handling] Check the HTTP status code to detect errors. Non-200 responses return JSON error bodies instead of image data. ```bash HTTP_CODE=$(curl -s -o response.bin -w "%{http_code}" \ "https://screenshotapi.to/api/v1/screenshot?url=https://example.com" \ -H "x-api-key: $SCREENSHOTAPI_KEY") if [ "$HTTP_CODE" -eq 200 ]; then mv response.bin screenshot.png echo "Screenshot saved" elif [ "$HTTP_CODE" -eq 402 ]; then echo "Out of credits" cat response.bin elif [ "$HTTP_CODE" -eq 403 ]; then echo "Invalid API key" cat response.bin else echo "Error $HTTP_CODE:" cat response.bin fi rm -f response.bin ``` --- # Elixir (/docs/sdks/elixir) There's no official Elixir/Hex package yet. The examples below call the [REST API](/docs/api/screenshot) directly with `Req` — copy-paste ready. Prefer a native client? Official SDKs ship for [JavaScript](/docs/sdks/javascript), [Python](/docs/sdks/python), [Go](/docs/sdks/go), [Ruby](/docs/sdks/ruby), and [PHP](/docs/sdks/php). Overview [#overview] These examples use [Req](https://hex.pm/packages/req), the modern HTTP client for Elixir. Add it to your `mix.exs`: ```elixir defp deps do [{:req, "~> 0.5"}] end ``` Quick Start [#quick-start] Set your API key [#set-your-api-key] ```bash export SCREENSHOTAPI_KEY="sk_live_your_key_here" ``` Take a screenshot [#take-a-screenshot] ```elixir api_key = System.get_env("SCREENSHOTAPI_KEY") {:ok, response} = Req.get("https://screenshotapi.to/api/v1/screenshot", params: [url: "https://example.com"], headers: [{"x-api-key", api_key}] ) if response.status == 200 do File.write!("screenshot.png", response.body) credits = Req.Response.get_header(response, "x-credits-remaining") |> List.first() IO.puts("Credits remaining: #{credits}") else IO.puts(:stderr, "Error #{response.status}: #{response.body}") end ``` Client Module [#client-module] A reusable client with all screenshot options: ```elixir defmodule ScreenshotAPI do @base_url "https://screenshotapi.to" defstruct [:api_key, base_url: @base_url] defmodule Result do defstruct [:content, :content_type, :credits_remaining, :screenshot_id, :duration_ms] end def new(api_key, opts \\ []) do %__MODULE__{ api_key: api_key, base_url: Keyword.get(opts, :base_url, @base_url) } end def capture(%__MODULE__{} = client, url, opts \\ []) do params = [url: url] |> maybe_add(:width, opts[:width]) |> maybe_add(:height, opts[:height]) |> maybe_add(:fullPage, if(opts[:full_page], do: "true")) |> maybe_add(:type, opts[:format]) |> maybe_add(:quality, opts[:quality]) |> maybe_add(:colorScheme, opts[:color_scheme]) |> maybe_add(:waitUntil, opts[:wait_until]) |> maybe_add(:waitForSelector, opts[:wait_for_selector]) |> maybe_add(:delay, opts[:delay]) case Req.get("#{client.base_url}/api/v1/screenshot", params: params, headers: [{"x-api-key", client.api_key}] ) do {:ok, %{status: 200} = resp} -> {:ok, %Result{ content: resp.body, content_type: get_header(resp, "content-type") || "image/png", credits_remaining: get_header(resp, "x-credits-remaining") |> to_int(), screenshot_id: get_header(resp, "x-screenshot-id") || "", duration_ms: get_header(resp, "x-duration-ms") |> to_int() }} {:ok, resp} -> {:error, "Screenshot failed (#{resp.status}): #{inspect(resp.body)}"} {:error, reason} -> {:error, "Request failed: #{inspect(reason)}"} end end defp maybe_add(params, _key, nil), do: params defp maybe_add(params, key, value), do: Keyword.put(params, key, value) defp get_header(resp, name) do Req.Response.get_header(resp, name) |> List.first() end defp to_int(nil), do: 0 defp to_int(val) when is_binary(val), do: String.to_integer(val) end ``` Usage [#usage] ```elixir api = ScreenshotAPI.new(System.get_env("SCREENSHOTAPI_KEY")) {:ok, result} = ScreenshotAPI.capture(api, "https://github.com", width: 1280, height: 720, format: "webp", quality: 85 ) File.write!("github.webp", result.content) IO.puts("Credits remaining: #{result.credits_remaining}") IO.puts("Duration: #{result.duration_ms}ms") ``` Common Patterns [#common-patterns] Batch Screenshots [#batch-screenshots] Capture multiple URLs concurrently with `Task.async_stream`: ```elixir urls = [ "https://example.com", "https://github.com", "https://news.ycombinator.com" ] urls |> Enum.with_index() |> Task.async_stream(fn {url, i} -> case ScreenshotAPI.capture(api, url) do {:ok, result} -> File.write!("screenshot-#{i}.png", result.content) IO.puts("✓ #{url}") {:error, reason} -> IO.puts(:stderr, "✗ #{url}: #{reason}") end end, max_concurrency: 5) |> Stream.run() ``` Phoenix Controller [#phoenix-controller] Serve screenshots in a Phoenix application: ```elixir defmodule MyAppWeb.ScreenshotController do use MyAppWeb, :controller @api ScreenshotAPI.new(System.get_env("SCREENSHOTAPI_KEY")) def show(conn, %{"url" => url}) do case ScreenshotAPI.capture(@api, url, format: "webp", quality: 80) do {:ok, result} -> conn |> put_resp_content_type(result.content_type) |> put_resp_header("cache-control", "public, max-age=3600") |> send_resp(200, result.content) {:error, _reason} -> conn |> put_status(502) |> json(%{error: "Screenshot failed"}) end end def show(conn, _params) do conn |> put_status(400) |> json(%{error: "url is required"}) end end ``` GenServer for Rate Limiting [#genserver-for-rate-limiting] Wrap the client in a GenServer with built-in rate limiting: ```elixir defmodule ScreenshotWorker do use GenServer def start_link(api_key) do GenServer.start_link(__MODULE__, api_key, name: __MODULE__) end def capture(url, opts \\ []) do GenServer.call(__MODULE__, {:capture, url, opts}, 30_000) end @impl true def init(api_key) do {:ok, %{api: ScreenshotAPI.new(api_key)}} end @impl true def handle_call({:capture, url, opts}, _from, state) do result = ScreenshotAPI.capture(state.api, url, opts) {:reply, result, state} end end ``` Error Handling [#error-handling] ```elixir case ScreenshotAPI.capture(api, "https://example.com") do {:ok, result} -> File.write!("screenshot.png", result.content) {:error, message} when message =~ "402" -> IO.puts(:stderr, "Out of credits — purchase more at screenshotapi.to") {:error, message} when message =~ "403" -> IO.puts(:stderr, "Invalid API key — check your configuration") {:error, message} -> IO.puts(:stderr, "Screenshot failed: #{message}") end ``` --- # Go (/docs/sdks/go) The official **[`screenshotapi-go`](https://pkg.go.dev/github.com/miketromba/screenshotapi-go)** SDK is a typed, context-aware client built on the standard library — no third-party dependencies. It uses typed constants for enums and `errors.As` for error handling. **Module:** `github.com/miketromba/screenshotapi-go` · **Reference:** [pkg.go.dev](https://pkg.go.dev/github.com/miketromba/screenshotapi-go) · Requires Go 1.21+. Installation [#installation] ```bash go get github.com/miketromba/screenshotapi-go ``` ```go import screenshotapi "github.com/miketromba/screenshotapi-go" ``` Authentication [#authentication] Create an API key in the [dashboard](https://screenshotapi.to/dashboard/api-keys) and keep it in an environment variable. The SDK sends it in the `x-api-key` header. ```bash export SCREENSHOTAPI_KEY="sk_live_your_key_here" ``` ```go client := screenshotapi.NewClient(os.Getenv("SCREENSHOTAPI_KEY")) ``` Quick Start [#quick-start] Capture a URL and save it to disk [#capture-a-url-and-save-it-to-disk] `Save` captures the screenshot, writes the file, and returns the response metadata. Every call takes a `context.Context` so you can apply deadlines and cancellation. ```go package main import ( "context" "fmt" "log" "os" screenshotapi "github.com/miketromba/screenshotapi-go" ) func main() { apiKey := os.Getenv("SCREENSHOTAPI_KEY") if apiKey == "" { log.Fatal("SCREENSHOTAPI_KEY is required") } client := screenshotapi.NewClient(apiKey) metadata, err := client.Save(context.Background(), screenshotapi.ScreenshotOptions{ URL: "https://example.com", }, "screenshot.png") if err != nil { log.Fatal(err) } fmt.Printf("Saved screenshot.png — credits remaining: %d\n", metadata.CreditsRemaining) } ``` Or work with the raw image bytes [#or-work-with-the-raw-image-bytes] `Screenshot` returns a `*Result` with the image bytes in memory. ```go result, err := client.Screenshot(ctx, screenshotapi.ScreenshotOptions{ URL: "https://example.com", Type: screenshotapi.WebP, Quality: 85, }) if err != nil { return err } fmt.Println(result.ContentType) // "image/webp" fmt.Println(len(result.Image)) // image size in bytes fmt.Println(result.Metadata.ScreenshotID) ``` Methods [#methods] `screenshotapi.NewClient(apiKey, ...Option)` [#screenshotapinewclientapikey-option] Configure the client with functional options: ```go client := screenshotapi.NewClient(apiKey, screenshotapi.WithTimeout(30*time.Second), screenshotapi.WithBaseURL("https://screenshotapi.to"), screenshotapi.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), ) ``` | Option | Description | | ------------------------------ | -------------------------------------------------------------------------------- | | `WithTimeout(time.Duration)` | HTTP client timeout (default `60s`) | | `WithBaseURL(string)` | API base URL — for proxies, gateways, tests (default `https://screenshotapi.to`) | | `WithHTTPClient(*http.Client)` | Supply your own `*http.Client` | `client.Screenshot(ctx, opts) (*Result, error)` [#clientscreenshotctx-opts-result-error] ```go type Result struct { Image []byte // raw image or PDF bytes ContentType string // "image/png", "image/webp", "application/pdf", … Metadata Metadata } type Metadata struct { CreditsRemaining int ScreenshotID string // include this when contacting support DurationMs int } ``` `client.Save(ctx, opts, path) (*Metadata, error)` [#clientsavectx-opts-path-metadata-error] Captures, writes the bytes to `path`, and returns the `*Metadata` shown above. Options [#options] `ScreenshotOptions` uses typed constants for enum fields, so the compiler catches invalid values. | Field | Type | Description | | --------------------- | -------------- | ------------------------------------------------------------- | | `URL` | `string` | URL to capture (required unless `HTML` is set) | | `HTML` | `string` | HTML document to render (switches to POST) | | `Width` | `int` | Viewport width in pixels (max 1920) | | `Height` | `int` | Viewport height in pixels (max 10000) | | `FullPage` | `bool` | Capture the full scrollable page | | `Type` | `ImageType` | `PNG`, `JPEG`, `WebP`, or `PDF` | | `Quality` | `int` | JPEG/WebP quality, 1–100 | | `ColorScheme` | `ColorScheme` | `Light` or `Dark` | | `WaitUntil` | `WaitUntil` | `Load`, `DOMContentLoaded`, `NetworkIdle0`, `NetworkIdle2` | | `WaitForSelector` | `string` | CSS selector to wait for | | `Delay` | `int` | Extra wait after load (ms, max 30000) | | `BlockAds` | `bool` | Block common ad networks | | `RemoveCookieBanners` | `bool` | Auto-remove cookie consent dialogs | | `CSSInject` | `string` | CSS injected before capture | | `JSInject` | `string` | JavaScript evaluated before capture | | `StealthMode` | `bool` | Anti-bot-detection browser fingerprint | | `DevicePixelRatio` | `int` | Retina/HiDPI scale (1, 2, or 3) | | `Timezone` | `string` | IANA timezone, e.g. `America/New_York` | | `Locale` | `string` | BCP 47 locale, e.g. `en-US` | | `CacheTTL` | `int` | Cache identical captures for N seconds | | `PreloadFonts` | `bool` | Preload Google Fonts before capture | | `RemoveElements` | `[]string` | CSS selectors to remove | | `RemovePopups` | `bool` | Remove common modals/overlays | | `MockupDevice` | `MockupDevice` | `BrowserMockup`, `IPhoneMockup`, `MacBookMockup` (PNG output) | | `Geolocation` | `*Geolocation` | `&screenshotapi.Geolocation{Latitude, Longitude, Accuracy}` | ```go result, err := client.Screenshot(ctx, screenshotapi.ScreenshotOptions{ URL: "https://example.com", Width: 1440, Height: 900, FullPage: true, Type: screenshotapi.WebP, Quality: 85, ColorScheme: screenshotapi.Dark, WaitUntil: screenshotapi.NetworkIdle2, WaitForSelector: "#app-ready", Delay: 250, BlockAds: true, RemoveCookieBanners: true, DevicePixelRatio: 2, CacheTTL: 300, RemoveElements: []string{".popup", "#promo-banner"}, Geolocation: &screenshotapi.Geolocation{ Latitude: 37.7749, Longitude: -122.4194, Accuracy: 25, }, }) ``` Device mockups (`MockupDevice`) always output PNG. Don't combine a mockup with `FullPage` or `Type: screenshotapi.PDF`. Render HTML & generate PDFs [#render-html--generate-pdfs] Set `HTML` to render a raw string — the SDK automatically issues a POST request. Combine with `Type: screenshotapi.PDF` for documents. ```go result, err := client.Screenshot(ctx, screenshotapi.ScreenshotOptions{ HTML: "

Hello from Go

", Type: screenshotapi.PDF, }) ``` Error Handling [#error-handling] The SDK returns typed errors. Use `errors.As` to match them. ```go import "errors" result, err := client.Screenshot(ctx, screenshotapi.ScreenshotOptions{ URL: "https://example.com", }) if err != nil { var authErr *screenshotapi.AuthenticationError var creditsErr *screenshotapi.InsufficientCreditsError var keyErr *screenshotapi.InvalidAPIKeyError var failedErr *screenshotapi.ScreenshotFailedError switch { case errors.As(err, &authErr): // 401: API key missing or malformed. case errors.As(err, &creditsErr): fmt.Printf("Out of credits, balance: %d\n", creditsErr.Balance) case errors.As(err, &keyErr): // 403: API key revoked or invalid. case errors.As(err, &failedErr): // 500: capture failed server-side. default: // Network errors, validation errors, or other API responses. } return err } _ = result ``` | Error | When | | --------------------------- | ------------------------------------------------------------------------------ | | `*AuthenticationError` | `401` — API key missing or malformed | | `*InsufficientCreditsError` | `402` — no credits remaining (exposes `.Balance`) | | `*InvalidAPIKeyError` | `403` — API key revoked or invalid | | `*ScreenshotFailedError` | `500` — capture failed server-side | | `*APIError` | Base type for other API responses (exposes `.StatusCode`, `.Code`, `.Message`) | Framework Recipe [#framework-recipe] net/http handler [#nethttp-handler] `Screenshot` accepts the request context, so cancellation propagates automatically. ```go func screenshotHandler(client *screenshotapi.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { url := r.URL.Query().Get("url") if url == "" { http.Error(w, `{"error":"url is required"}`, http.StatusBadRequest) return } result, err := client.Screenshot(r.Context(), screenshotapi.ScreenshotOptions{ URL: url, Type: screenshotapi.WebP, Quality: 80, }) if err != nil { http.Error(w, `{"error":"screenshot failed"}`, http.StatusBadGateway) return } w.Header().Set("Content-Type", result.ContentType) w.Header().Set("Cache-Control", "public, max-age=3600") w.Write(result.Image) } } ``` Next steps [#next-steps] * [Screenshot API reference](/docs/api/screenshot) — every parameter in detail * [Authentication](/docs/authentication) — create and rotate API keys * [Credits](/docs/credits) — how billing works (200 free screenshots/month) * [Integrations](/docs/integrations) — framework and platform guides --- # SDKs & Libraries (/docs/sdks) ScreenshotAPI ships **official, typed SDKs** for five languages. Each one wraps the [REST API](/docs/api/screenshot) with the same ergonomic surface: a `screenshot()` method for raw bytes, a `save()` helper that writes a file, typed errors, and every screenshot option as a first-class argument. Official SDKs [#official-sdks] | Language | Package | Install | | --------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------- | | **JavaScript / TypeScript** | [`screenshotapi-to`](https://www.npmjs.com/package/screenshotapi-to) (npm) | `npm install screenshotapi-to` | | **Python** | [`screenshotapi-to`](https://pypi.org/project/screenshotapi-to/) (PyPI) | `pip install screenshotapi-to` | | **Go** | [`screenshotapi-go`](https://pkg.go.dev/github.com/miketromba/screenshotapi-go) | `go get github.com/miketromba/screenshotapi-go` | | **Ruby** | [`screenshotapi_to`](https://rubygems.org/gems/screenshotapi_to) (RubyGems) | `gem install screenshotapi_to` | | **PHP** | [`screenshotapi/sdk`](https://packagist.org/packages/screenshotapi/sdk) (Packagist) | `composer require screenshotapi/sdk` | Each successful screenshot consumes **1 credit**, and every account includes **200 free screenshots per month**. See [Credits](/docs/credits) for details. Command line [#command-line] Other languages [#other-languages] There isn't an official native package for these yet, but the API is a single HTTP call. These guides have copy-paste, idiomatic examples using each language's standard HTTP client. Working in something else entirely? Any language that can make an HTTP request works — see the [Screenshot API reference](/docs/api/screenshot) for the full endpoint contract. What every SDK gives you [#what-every-sdk-gives-you] * **`screenshot()`** — capture a URL (or rendered HTML) and get back the raw image/PDF bytes plus metadata (`creditsRemaining`, `screenshotId`, `durationMs`). * **`save()`** — capture and write the file to disk in one call. * **Typed errors** — distinct types for authentication, invalid keys, insufficient credits, and capture failures, so you can handle each case precisely. * **Every option** — full-page captures, dark mode, ad/cookie-banner removal, device mockups, geolocation, caching, HTML rendering, PDF export, and more. See each SDK's options table or the [API reference](/docs/api/screenshot). --- # Java (/docs/sdks/java) There's no official Java SDK yet. The examples below call the [REST API](/docs/api/screenshot) directly with Java's built-in `HttpClient` — no dependencies, copy-paste ready. Prefer a native client? Official SDKs ship for [JavaScript](/docs/sdks/javascript), [Python](/docs/sdks/python), [Go](/docs/sdks/go), [Ruby](/docs/sdks/ruby), and [PHP](/docs/sdks/php). Overview [#overview] These examples use Java's built-in `HttpClient` (Java 11+). No external dependencies are required. Quick Start [#quick-start] Set your API key [#set-your-api-key] ```bash export SCREENSHOTAPI_KEY="sk_live_your_key_here" ``` Take a screenshot [#take-a-screenshot] ```java import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.file.Files; import java.nio.file.Path; public class Screenshot { public static void main(String[] args) throws Exception { String apiKey = System.getenv("SCREENSHOTAPI_KEY"); HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://screenshotapi.to/api/v1/screenshot?url=https://example.com")) .header("x-api-key", apiKey) .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofByteArray()); if (response.statusCode() != 200) { System.err.println("Error " + response.statusCode() + ": " + new String(response.body())); System.exit(1); } Files.write(Path.of("screenshot.png"), response.body()); System.out.println("Credits remaining: " + response.headers().firstValue("x-credits-remaining").orElse("unknown")); } } ``` Client Class [#client-class] A reusable client with all screenshot options: ```java import java.net.URI; import java.net.URLEncoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.Map; public class ScreenshotAPI { private final String apiKey; private final String baseUrl; private final HttpClient client; public ScreenshotAPI(String apiKey) { this(apiKey, "https://screenshotapi.to"); } public ScreenshotAPI(String apiKey, String baseUrl) { this.apiKey = apiKey; this.baseUrl = baseUrl; this.client = HttpClient.newHttpClient(); } public record ScreenshotResult( byte[] content, String contentType, int creditsRemaining, String screenshotId, int durationMs ) {} public ScreenshotResult capture(String url) throws Exception { return capture(url, Map.of()); } public ScreenshotResult capture(String url, Map options) throws Exception { Map params = new LinkedHashMap<>(); params.put("url", url); params.putAll(options); String query = params.entrySet().stream() .map(e -> URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8) + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8)) .reduce((a, b) -> a + "&" + b) .orElse(""); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(baseUrl + "/api/v1/screenshot?" + query)) .header("x-api-key", apiKey) .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofByteArray()); if (response.statusCode() != 200) { throw new RuntimeException( "Screenshot failed (" + response.statusCode() + "): " + new String(response.body())); } return new ScreenshotResult( response.body(), response.headers().firstValue("content-type").orElse("image/png"), Integer.parseInt(response.headers().firstValue("x-credits-remaining").orElse("0")), response.headers().firstValue("x-screenshot-id").orElse(""), Integer.parseInt(response.headers().firstValue("x-duration-ms").orElse("0")) ); } } ``` Usage [#usage] ```java ScreenshotAPI api = new ScreenshotAPI(System.getenv("SCREENSHOTAPI_KEY")); ScreenshotAPI.ScreenshotResult result = api.capture("https://github.com", Map.of( "width", "1280", "height", "720", "type", "webp", "quality", "85" )); Files.write(Path.of("github.webp"), result.content()); System.out.println("Credits remaining: " + result.creditsRemaining()); System.out.println("Duration: " + result.durationMs() + "ms"); ``` Common Patterns [#common-patterns] Batch Screenshots [#batch-screenshots] Process multiple URLs concurrently with virtual threads (Java 21+): ```java import java.util.List; import java.util.concurrent.StructuredTaskScope; List urls = List.of( "https://example.com", "https://github.com", "https://news.ycombinator.com" ); try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { List> tasks = urls.stream() .map(url -> scope.fork(() -> api.capture(url))) .toList(); scope.join(); for (int i = 0; i < tasks.size(); i++) { ScreenshotAPI.ScreenshotResult result = tasks.get(i).get(); Files.write(Path.of("screenshot-" + i + ".png"), result.content()); System.out.println("✓ " + urls.get(i)); } } ``` Spring Boot Controller [#spring-boot-controller] Serve screenshots in a Spring Boot application: ```java import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class ScreenshotController { private final ScreenshotAPI api; public ScreenshotController() { this.api = new ScreenshotAPI(System.getenv("SCREENSHOTAPI_KEY")); } @GetMapping("/screenshot") public ResponseEntity screenshot(@RequestParam String url) { try { ScreenshotAPI.ScreenshotResult result = api.capture(url, Map.of( "type", "webp", "quality", "80" )); return ResponseEntity.ok() .contentType(MediaType.parseMediaType(result.contentType())) .header("Cache-Control", "public, max-age=3600") .body(result.content()); } catch (Exception e) { return ResponseEntity.internalServerError().build(); } } } ``` Error Handling [#error-handling] ```java try { ScreenshotAPI.ScreenshotResult result = api.capture("https://example.com"); } catch (RuntimeException e) { String message = e.getMessage(); if (message.contains("402")) { System.err.println("Out of credits — purchase more at screenshotapi.to"); } else if (message.contains("403")) { System.err.println("Invalid API key — check your configuration"); } else { System.err.println("Screenshot failed: " + message); } } ``` --- # JavaScript / TypeScript (/docs/sdks/javascript) The official **[`screenshotapi-to`](https://www.npmjs.com/package/screenshotapi-to)** SDK is a fully typed client with zero runtime dependencies. It works anywhere native `fetch` is available — Node.js 18+, Next.js, Vercel Functions, Bun, Deno, and Cloudflare Workers. **Package:** [`screenshotapi-to`](https://www.npmjs.com/package/screenshotapi-to) on npm · **Source & examples:** [github.com/miketromba/screenshotapi-js](https://github.com/miketromba/screenshotapi-js) · Prefer raw HTTP? See [Without the SDK](#without-the-sdk). Installation [#installation] ```bash npm install screenshotapi-to ``` ```bash pnpm add screenshotapi-to ``` ```bash yarn add screenshotapi-to ``` ```bash bun add screenshotapi-to ``` Authentication [#authentication] Create an API key in the [dashboard](https://screenshotapi.to/dashboard/api-keys) and keep it in a server-side environment variable. ```bash export SCREENSHOTAPI_KEY="sk_live_your_key_here" ``` ```typescript import { ScreenshotAPI } from 'screenshotapi-to' const client = new ScreenshotAPI({ apiKey: process.env.SCREENSHOTAPI_KEY! }) ``` Never expose your API key in browser JavaScript. For client-side apps, call your own backend route and let that route use the SDK. See [Browser usage](#browser-usage). Quick Start [#quick-start] Capture a URL and save it to disk [#capture-a-url-and-save-it-to-disk] `save()` captures the screenshot and writes the file in one call. It returns the response metadata. ```typescript import { ScreenshotAPI } from 'screenshotapi-to' const client = new ScreenshotAPI({ apiKey: process.env.SCREENSHOTAPI_KEY! }) const metadata = await client.save({ url: 'https://example.com', path: './example.png', type: 'png' }) console.log(`Screenshot ID: ${metadata.screenshotId}`) console.log(`Credits remaining: ${metadata.creditsRemaining}`) console.log(`Captured in ${metadata.durationMs}ms`) ``` Or work with the raw image bytes [#or-work-with-the-raw-image-bytes] `screenshot()` returns the image as an `ArrayBuffer` so you can stream it, upload it, or transform it. It runs in any runtime, including edge. ```typescript const result = await client.screenshot({ url: 'https://example.com', type: 'webp', quality: 85 }) console.log(result.contentType) // "image/webp" console.log(result.image.byteLength) // image size in bytes console.log(result.metadata.creditsRemaining) ``` `save()` writes files with Node.js APIs and is only available in Node-compatible runtimes. In edge runtimes (Cloudflare Workers, Vercel Edge), use `screenshot()` and return or store the `ArrayBuffer` yourself. Methods [#methods] `new ScreenshotAPI(config)` [#new-screenshotapiconfig] | Option | Type | Default | Description | | --------- | -------- | -------------------------- | ------------------------------- | | `apiKey` | `string` | — (required) | Your API key | | `baseUrl` | `string` | `https://screenshotapi.to` | API base URL (proxies, tests) | | `timeout` | `number` | `60000` | Request timeout in milliseconds | `client.screenshot(options)` [#clientscreenshotoptions] Returns `Promise`: ```typescript interface ScreenshotResult { image: ArrayBuffer // raw image (or PDF) bytes contentType: string // "image/png", "image/webp", "application/pdf", … metadata: { creditsRemaining: number screenshotId: string // include this when contacting support durationMs: number } } ``` `client.save(options)` [#clientsaveoptions] Takes the same options as `screenshot()` **plus** `path: string`. Writes the file to `path` and returns `Promise` (the `metadata` shape above). Node-only. Options [#options] Every [screenshot parameter](/docs/api/screenshot) is available as a camelCase option on `screenshot()` and `save()`. | Option | Type | Default | Description | | --------------------- | ------------------------------------------------------------------ | ----------------------------- | ------------------------------------------ | | `url` | `string` | Required unless `html` is set | URL to capture | | `html` | `string` | — | HTML document to render (switches to POST) | | `width` | `number` | `1440` | Viewport width in pixels (max 1920) | | `height` | `number` | `900` | Viewport height in pixels (max 10000) | | `fullPage` | `boolean` | `false` | Capture the full scrollable page | | `type` | `'png' \| 'jpeg' \| 'webp' \| 'pdf'` | `'png'` | Output format | | `quality` | `number` | `100` | JPEG/WebP quality, 1–100 | | `colorScheme` | `'light' \| 'dark'` | Page default | Force `prefers-color-scheme` | | `waitUntil` | `'load' \| 'domcontentloaded' \| 'networkidle0' \| 'networkidle2'` | `'networkidle2'` | Page readiness signal | | `waitForSelector` | `string` | — | CSS selector to wait for | | `delay` | `number` | `0` | Extra wait after load (ms, max 30000) | | `blockAds` | `boolean` | `false` | Block common ad networks | | `removeCookieBanners` | `boolean` | `false` | Auto-remove cookie consent dialogs | | `cssInject` | `string` | — | CSS injected before capture | | `jsInject` | `string` | — | JavaScript evaluated before capture | | `stealthMode` | `boolean` | `false` | Anti-bot-detection browser fingerprint | | `devicePixelRatio` | `1 \| 2 \| 3` | `1` | Retina/HiDPI scale | | `timezone` | `string` | Server default | IANA timezone, e.g. `America/New_York` | | `locale` | `string` | Server default | BCP 47 locale, e.g. `en-US` | | `cacheTtl` | `number` | `0` | Cache identical captures for N seconds | | `preloadFonts` | `boolean` | `false` | Preload Google Fonts before capture | | `removeElements` | `string[]` | — | CSS selectors to remove | | `removePopups` | `boolean` | `false` | Remove common modals/overlays | | `mockupDevice` | `'browser' \| 'iphone' \| 'macbook'` | — | Wrap output in a device frame (PNG) | | `geoLocation` | `{ latitude: number; longitude: number; accuracy?: number }` | — | Browser geolocation emulation | ```typescript const result = await client.screenshot({ url: 'https://example.com/dashboard', width: 1440, height: 1200, fullPage: true, type: 'webp', quality: 90, colorScheme: 'dark', waitUntil: 'networkidle0', waitForSelector: '[data-ready="true"]', delay: 500, blockAds: true, removeCookieBanners: true, devicePixelRatio: 2, timezone: 'America/New_York', locale: 'en-US', cacheTtl: 300, removeElements: ['.modal', '#promo'], geoLocation: { latitude: 40.7128, longitude: -74.006, accuracy: 25 } }) ``` Render HTML & generate PDFs [#render-html--generate-pdfs] Pass `html` instead of `url` to render a raw HTML string — the SDK automatically switches to `POST /api/v1/screenshot`. Combine with `type: 'pdf'` to produce documents like invoices or reports. ```typescript const pdf = await client.screenshot({ html: '

Invoice #1024

Thank you for your business.

', type: 'pdf', width: 1200 }) // pdf.image is an ArrayBuffer of the PDF bytes ``` Error Handling [#error-handling] The SDK throws typed errors for API, network, and timeout failures. Every error extends `ScreenshotAPIError`. ```typescript import { ScreenshotAPI, AuthenticationError, InvalidAPIKeyError, InsufficientCreditsError, ScreenshotFailedError, ScreenshotTimeoutError, ScreenshotNetworkError } from 'screenshotapi-to' const client = new ScreenshotAPI({ apiKey: process.env.SCREENSHOTAPI_KEY!, timeout: 30_000 }) try { await client.screenshot({ url: 'https://example.com' }) } catch (error) { if (error instanceof AuthenticationError) { console.error('Missing or malformed API key (401)') } else if (error instanceof InvalidAPIKeyError) { console.error('API key is revoked or invalid (403)') } else if (error instanceof InsufficientCreditsError) { console.error(`Out of credits (402). Balance: ${error.balance}`) } else if (error instanceof ScreenshotFailedError) { console.error(`Capture failed server-side (500): ${error.message}`) } else if (error instanceof ScreenshotTimeoutError) { console.error(`Timed out after ${error.timeoutMs}ms`) } else if (error instanceof ScreenshotNetworkError) { console.error('Could not reach ScreenshotAPI') } else { throw error } } ``` | Error | When | | -------------------------- | ------------------------------------------------- | | `AuthenticationError` | `401` — API key missing or malformed | | `InsufficientCreditsError` | `402` — no credits remaining (exposes `.balance`) | | `InvalidAPIKeyError` | `403` — API key revoked or invalid | | `ScreenshotFailedError` | `500` — capture failed server-side | | `ScreenshotTimeoutError` | Request exceeded `timeout` (exposes `.timeoutMs`) | | `ScreenshotNetworkError` | Network/connection failure | | `ScreenshotAPIError` | Base class for all of the above | Framework Recipes [#framework-recipes] Next.js (App Router) [#nextjs-app-router] Proxy screenshots through a route handler so your key stays on the server. ```typescript // app/api/screenshot/route.ts import { ScreenshotAPI } from 'screenshotapi-to' const client = new ScreenshotAPI({ apiKey: process.env.SCREENSHOTAPI_KEY! }) export async function GET(request: Request) { const url = new URL(request.url).searchParams.get('url') if (!url) { return Response.json({ error: 'url is required' }, { status: 400 }) } try { const { image, contentType } = await client.screenshot({ url, type: 'webp' }) return new Response(image, { headers: { 'Content-Type': contentType, 'Cache-Control': 'public, max-age=3600' } }) } catch { return Response.json({ error: 'Screenshot failed' }, { status: 500 }) } } ``` Express [#express] ```typescript import express from 'express' import { ScreenshotAPI } from 'screenshotapi-to' const app = express() const client = new ScreenshotAPI({ apiKey: process.env.SCREENSHOTAPI_KEY! }) app.get('/screenshot', async (req, res) => { const url = req.query.url as string if (!url) return res.status(400).json({ error: 'url is required' }) try { const { image, contentType } = await client.screenshot({ url, type: 'webp', quality: 80 }) res.set('Content-Type', contentType) res.set('Cache-Control', 'public, max-age=3600') res.send(Buffer.from(image)) } catch { res.status(500).json({ error: 'Screenshot failed' }) } }) ``` Cloudflare Workers [#cloudflare-workers] `save()` isn't available at the edge — use `screenshot()` and return the `ArrayBuffer` directly. ```typescript import { ScreenshotAPI } from 'screenshotapi-to' export default { async fetch(request: Request, env: { SCREENSHOTAPI_KEY: string }): Promise { const client = new ScreenshotAPI({ apiKey: env.SCREENSHOTAPI_KEY }) const url = new URL(request.url).searchParams.get('url') ?? 'https://example.com' const { image, contentType } = await client.screenshot({ url, type: 'webp' }) return new Response(image, { headers: { 'Content-Type': contentType } }) } } ``` Batch captures [#batch-captures] `screenshot()` is a normal promise, so use `Promise.allSettled` for resilient parallel captures. ```typescript const urls = ['https://example.com', 'https://github.com', 'https://news.ycombinator.com'] const results = await Promise.allSettled(urls.map(url => client.save({ url, path: `./out/${new URL(url).hostname}.png` }))) results.forEach((result, i) => { if (result.status === 'fulfilled') console.log(`✓ ${urls[i]}`) else console.error(`✗ ${urls[i]}: ${result.reason.message}`) }) ``` More runnable examples ship with the package: [Node.js](https://github.com/miketromba/screenshotapi-js/blob/main/examples/node.js), [Next.js](https://github.com/miketromba/screenshotapi-js/blob/main/examples/nextjs-route.ts), [Vercel Function](https://github.com/miketromba/screenshotapi-js/blob/main/examples/vercel-function.ts), and [Cloudflare Worker](https://github.com/miketromba/screenshotapi-js/blob/main/examples/cloudflare-worker.ts). Browser usage [#browser-usage] Never call ScreenshotAPI directly from the browser — it would expose your API key. Always proxy through a backend route (see the [Next.js](#nextjs-app-router) and [Express](#express) recipes above). ```typescript // Frontend — calls your own proxy, not ScreenshotAPI const response = await fetch(`/api/screenshot?url=${encodeURIComponent(url)}`) const blob = await response.blob() const imageUrl = URL.createObjectURL(blob) ``` Without the SDK [#without-the-sdk] The SDK is a thin wrapper over a single HTTP endpoint, so you can always call it with native `fetch` and no dependencies: ```typescript const params = new URLSearchParams({ url: 'https://example.com' }) const response = await fetch(`https://screenshotapi.to/api/v1/screenshot?${params}`, { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } }) if (!response.ok) throw new Error((await response.json()).error) const image = Buffer.from(await response.arrayBuffer()) ``` See the [Screenshot API reference](/docs/api/screenshot) for the full endpoint contract. Requirements [#requirements] * Node.js 18+ (or any runtime with native `fetch`) * Zero runtime dependencies Next steps [#next-steps] * [Screenshot API reference](/docs/api/screenshot) — every parameter in detail * [Authentication](/docs/authentication) — create and rotate API keys * [Credits](/docs/credits) — how billing works (200 free screenshots/month) * [Integrations](/docs/integrations) — framework and platform guides --- # PHP (/docs/sdks/php) The official **[`screenshotapi/sdk`](https://packagist.org/packages/screenshotapi/sdk)** package is a typed, Guzzle-powered client. It captures screenshots, PDFs, and rendered HTML and throws typed exceptions for API failures. **Package:** [`screenshotapi/sdk`](https://packagist.org/packages/screenshotapi/sdk) on Packagist · **Namespace:** `ScreenshotAPI\` · **Source & examples:** [github.com/miketromba/screenshotapi-php](https://github.com/miketromba/screenshotapi-php) · PHP 8.1+. Installation [#installation] ```bash composer require screenshotapi/sdk ``` Requires PHP 8.1+ and Composer. Authentication [#authentication] Create an API key in the [dashboard](https://screenshotapi.to/dashboard/api-keys) and expose it to your app as an environment variable. The SDK sends it in the `x-api-key` header. ```bash export SCREENSHOTAPI_KEY=sk_live_your_key_here ``` ```php Keep API keys on the server. Never expose them in client-side code or commit them to source control. Quick Start [#quick-start] Capture a URL and save it to disk [#capture-a-url-and-save-it-to-disk] `save()` captures the screenshot, writes the file, and returns the response metadata. ```php save( new ScreenshotOptions(url: 'https://example.com'), __DIR__ . '/screenshot.png', ); echo "Screenshot ID: {$metadata->screenshotId}\n"; echo "Credits remaining: {$metadata->creditsRemaining}\n"; ``` Or work with the raw image bytes [#or-work-with-the-raw-image-bytes] `screenshot()` returns a `Result` with the image string plus metadata — ideal for streaming from a framework. ```php $result = $client->screenshot(new ScreenshotOptions( url: 'https://example.com', type: 'webp', quality: 85, )); file_put_contents('example.webp', $result->image); echo "{$result->contentType}\n"; // "image/webp" echo "{$result->metadata->durationMs}ms\n"; ``` Methods [#methods] `new Client(apiKey, baseUrl, timeout, httpClient)` [#new-clientapikey-baseurl-timeout-httpclient] | Parameter | Type | Default | Description | | ------------ | ---------------------------- | -------------------------- | ------------------------------- | | `apiKey` | `string` | — (required) | Your API key | | `baseUrl` | `string` | `https://screenshotapi.to` | API base URL (proxies, tests) | | `timeout` | `float` | `60` | Request timeout in seconds | | `httpClient` | `GuzzleHttp\ClientInterface` | internal Guzzle client | Custom Guzzle-compatible client | ```php use GuzzleHttp\Client as HttpClient; use ScreenshotAPI\Client; $client = new Client( apiKey: getenv('SCREENSHOTAPI_KEY'), baseUrl: 'https://screenshotapi.to', timeout: 30.0, httpClient: new HttpClient(['timeout' => 30.0]), ); ``` `$client->screenshot(ScreenshotOptions $options): Result` [#client-screenshotscreenshotoptions-options-result] `Result` is a readonly object: ```php $result->image // string — raw image or PDF bytes $result->contentType // "image/png", "image/webp", "application/pdf", … $result->metadata->creditsRemaining // int $result->metadata->screenshotId // string — include this when contacting support $result->metadata->durationMs // int ``` `$client->save(ScreenshotOptions $options, string $path): Metadata` [#client-savescreenshotoptions-options-string-path-metadata] Captures, writes the bytes to `$path`, and returns the `Metadata` shown above. Options [#options] Build a `ScreenshotOptions` with named arguments. Every [screenshot parameter](/docs/api/screenshot) is supported. | Option | Type | Default | Description | | --------------------- | -------------- | ----------------------------- | ---------------------------------------------------------- | | `url` | `string` | Required unless `html` is set | URL to capture | | `html` | `string` | — | HTML document to render (switches to POST) | | `width` | `int` | `1440` | Viewport width in pixels (max 1920) | | `height` | `int` | `900` | Viewport height in pixels (max 10000) | | `fullPage` | `bool` | `false` | Capture the full scrollable page | | `type` | `string` | `png` | `png`, `jpeg`, `webp`, or `pdf` | | `quality` | `int` | `100` | JPEG/WebP quality, 1–100 | | `colorScheme` | `string` | Page default | `light` or `dark` | | `waitUntil` | `string` | `networkidle2` | `load`, `domcontentloaded`, `networkidle0`, `networkidle2` | | `waitForSelector` | `string` | — | CSS selector to wait for | | `delay` | `int` | `0` | Extra wait after load (ms, max 30000) | | `blockAds` | `bool` | `false` | Block common ad networks | | `removeCookieBanners` | `bool` | `false` | Auto-remove cookie consent dialogs | | `cssInject` | `string` | — | CSS injected before capture | | `jsInject` | `string` | — | JavaScript evaluated before capture | | `stealthMode` | `bool` | `false` | Anti-bot-detection browser fingerprint | | `devicePixelRatio` | `int` | `1` | Retina/HiDPI scale (1, 2, or 3) | | `timezone` | `string` | Server default | IANA timezone, e.g. `America/New_York` | | `locale` | `string` | Server default | BCP 47 locale, e.g. `en-US` | | `cacheTtl` | `int` | `0` | Cache identical captures for N seconds | | `preloadFonts` | `bool` | `false` | Preload Google Fonts before capture | | `removeElements` | `list` | — | CSS selectors to remove | | `removePopups` | `bool` | `false` | Remove common modals/overlays | | `mockupDevice` | `string` | — | `browser`, `iphone`, or `macbook` (PNG output) | | `geoLocation` | `array` | — | `['latitude' => …, 'longitude' => …, 'accuracy' => …]` | ```php $result = $client->screenshot(new ScreenshotOptions( url: 'https://example.com/pricing', width: 1920, height: 1080, fullPage: true, type: 'webp', quality: 85, colorScheme: 'dark', waitUntil: 'networkidle0', waitForSelector: '#main', delay: 500, blockAds: true, removeCookieBanners: true, devicePixelRatio: 2, cacheTtl: 300, removeElements: ['.modal', '#promo'], geoLocation: ['latitude' => 40.7128, 'longitude' => -74.0060, 'accuracy' => 25], )); ``` Render HTML & generate PDFs [#render-html--generate-pdfs] Pass `html` to render a raw string — the SDK automatically switches to `POST /api/v1/screenshot`. Combine with `type: 'pdf'` for documents. ```php $pdf = $client->screenshot(new ScreenshotOptions( html: '

Invoice

', type: 'pdf', width: 1200, )); file_put_contents('invoice.pdf', $pdf->image); ``` Error Handling [#error-handling] The SDK throws typed exceptions. All extend `APIException`, so catch the specific ones first. ```php use ScreenshotAPI\ScreenshotOptions; use ScreenshotAPI\Exceptions\APIException; use ScreenshotAPI\Exceptions\AuthenticationException; use ScreenshotAPI\Exceptions\InvalidAPIKeyException; use ScreenshotAPI\Exceptions\InsufficientCreditsException; use ScreenshotAPI\Exceptions\ScreenshotFailedException; try { $result = $client->screenshot(new ScreenshotOptions(url: 'https://example.com')); } catch (AuthenticationException $e) { // 401: API key is missing or malformed. } catch (InvalidAPIKeyException $e) { // 403: API key is invalid or revoked. } catch (InsufficientCreditsException $e) { echo "Out of credits (402). Balance: {$e->balance}\n"; } catch (ScreenshotFailedException $e) { echo "Capture failed server-side (500): {$e->getMessage()}\n"; } catch (APIException $e) { echo "Request failed ({$e->statusCode}): {$e->getMessage()}\n"; } ``` | Exception | When | | ------------------------------ | ----------------------------------------------------------------------------------------------------- | | `AuthenticationException` | `401` — API key missing or malformed | | `InsufficientCreditsException` | `402` — no credits remaining (exposes `$balance`) | | `InvalidAPIKeyException` | `403` — API key invalid or revoked | | `ScreenshotFailedException` | `500` — capture failed server-side | | `APIException` | Base class (exposes `$statusCode`, `$errorCode`). Network failures use `errorCode: "request_failed"`. | Framework Recipes [#framework-recipes] Laravel [#laravel] ```php validate(['url' => 'required|url']); $client = new Client(config('services.screenshotapi.key')); try { $result = $client->screenshot(new ScreenshotOptions( url: $request->input('url'), type: 'webp', quality: 80, )); return response($result->image) ->header('Content-Type', $result->contentType) ->header('Cache-Control', 'public, max-age=3600'); } catch (APIException $e) { return response()->json(['error' => $e->getMessage()], 502); } } } ``` Symfony [#symfony] ```php query->get('url'); if (!$url) { return new Response('url is required', 400); } $client = new Client($_ENV['SCREENSHOTAPI_KEY']); $result = $client->screenshot(new ScreenshotOptions(url: $url, type: 'webp')); return new Response($result->image, 200, [ 'Content-Type' => $result->contentType, 'Cache-Control' => 'public, max-age=3600', ]); } } ``` Runnable examples ship with the package: [`plain-php.php`](https://github.com/miketromba/screenshotapi-php/blob/main/examples/plain-php.php), [`laravel-controller.php`](https://github.com/miketromba/screenshotapi-php/blob/main/examples/laravel-controller.php), and [`symfony-controller.php`](https://github.com/miketromba/screenshotapi-php/blob/main/examples/symfony-controller.php). Next steps [#next-steps] * [Screenshot API reference](/docs/api/screenshot) — every parameter in detail * [Authentication](/docs/authentication) — create and rotate API keys * [Credits](/docs/credits) — how billing works (200 free screenshots/month) * [Integrations](/docs/integrations) — framework and platform guides --- # Python (/docs/sdks/python) The official **[`screenshotapi-to`](https://pypi.org/project/screenshotapi-to/)** SDK is a fully typed Python client with sync and async APIs. It ships `py.typed` metadata, so editors and type checkers understand every option and return value. **Package:** [`screenshotapi-to`](https://pypi.org/project/screenshotapi-to/) on PyPI · **Import name:** `screenshotapi` · **Source & examples:** [github.com/miketromba/screenshotapi-python](https://github.com/miketromba/screenshotapi-python) · Python 3.8+. Installation [#installation] ```bash pip install screenshotapi-to ``` ```bash poetry add screenshotapi-to ``` ```bash uv add screenshotapi-to ``` The distribution package is `screenshotapi-to`; the **import package is `screenshotapi`**. Authentication [#authentication] Create an API key in the [dashboard](https://screenshotapi.to/dashboard/api-keys) and keep it in an environment variable. The SDK sends it in the `x-api-key` header. ```bash export SCREENSHOTAPI_KEY="sk_live_your_key_here" ``` ```python import os from screenshotapi import ScreenshotAPI client = ScreenshotAPI(os.environ["SCREENSHOTAPI_KEY"]) ``` Keep API keys on the server. Never ship them in browser bundles, mobile apps, or notebooks you share. Quick Start [#quick-start] Capture a URL and save it to disk [#capture-a-url-and-save-it-to-disk] `save()` captures the screenshot, writes the file, and returns the response metadata. ```python import os from screenshotapi import ScreenshotAPI client = ScreenshotAPI(os.environ["SCREENSHOTAPI_KEY"]) metadata = client.save( {"url": "https://example.com", "width": 1440, "height": 900}, "screenshot.png", ) print(f"Screenshot ID: {metadata.screenshot_id}") print(f"Credits remaining: {metadata.credits_remaining}") ``` Or work with the raw image bytes [#or-work-with-the-raw-image-bytes] `screenshot()` returns the image as `bytes` plus metadata. ```python result = client.screenshot({"url": "https://example.com", "type": "webp", "quality": 85}) print(result.content_type) # "image/webp" print(len(result.image)) # image size in bytes print(result.metadata.credits_remaining) ``` You can pass options as a plain `dict` (shown above) or as a typed `ScreenshotOptions` dataclass for editor autocomplete: ```python from screenshotapi import ScreenshotOptions result = client.screenshot( ScreenshotOptions( url="https://example.com", full_page=True, type="webp", quality=90, color_scheme="dark", ) ) ``` Async [#async] Every method has an `async_` counterpart for use inside `asyncio` event loops, FastAPI endpoints, or background workers. ```python import asyncio from screenshotapi import ScreenshotAPI client = ScreenshotAPI(os.environ["SCREENSHOTAPI_KEY"]) async def main() -> None: result = await client.async_screenshot({"url": "https://example.com"}) print(f"Captured in {result.metadata.duration_ms}ms") await client.async_save({"url": "https://example.com"}, "screenshot.png") asyncio.run(main()) ``` Methods [#methods] `ScreenshotAPI(api_key, *, base_url=..., timeout=60.0)` [#screenshotapiapi_key--base_url-timeout600] | Parameter | Type | Default | Description | | ---------- | ------- | -------------------------- | ----------------------------- | | `api_key` | `str` | — (required) | Your API key | | `base_url` | `str` | `https://screenshotapi.to` | API base URL (proxies, tests) | | `timeout` | `float` | `60.0` | Request timeout in seconds | `client.screenshot(options)` · `client.async_screenshot(options)` [#clientscreenshotoptions--clientasync_screenshotoptions] `options` is a `ScreenshotOptions` or a `dict`. Returns a `ScreenshotResult`: ```python result.image # bytes — raw image or PDF result.content_type # "image/png", "image/webp", "application/pdf", … result.metadata.credits_remaining # int result.metadata.screenshot_id # str — include this when contacting support result.metadata.duration_ms # int ``` `client.save(options, path)` · `client.async_save(options, path)` [#clientsaveoptions-path--clientasync_saveoptions-path] Captures, writes the bytes to `path`, and returns the `metadata` shown above. Options [#options] Every [screenshot parameter](/docs/api/screenshot) is available as a snake\_case field. | Option | Type | Default | Description | | ----------------------------------------------- | ------------------------------------------------------------------ | ----------------------------- | ------------------------------------------ | | `url` | `str` | Required unless `html` is set | URL to capture | | `html` | `str` | — | HTML document to render (switches to POST) | | `width` | `int` | `1440` | Viewport width in pixels (max 1920) | | `height` | `int` | `900` | Viewport height in pixels (max 10000) | | `full_page` | `bool` | `False` | Capture the full scrollable page | | `type` | `"png" \| "jpeg" \| "webp" \| "pdf"` | `"png"` | Output format | | `quality` | `int` | `100` | JPEG/WebP quality, 1–100 | | `color_scheme` | `"light" \| "dark"` | Page default | Force `prefers-color-scheme` | | `wait_until` | `"load" \| "domcontentloaded" \| "networkidle0" \| "networkidle2"` | `"networkidle2"` | Page readiness signal | | `wait_for_selector` | `str` | — | CSS selector to wait for | | `delay` | `int` | `0` | Extra wait after load (ms, max 30000) | | `block_ads` | `bool` | `False` | Block common ad networks | | `remove_cookie_banners` | `bool` | `False` | Auto-remove cookie consent dialogs | | `css_inject` | `str` | — | CSS injected before capture | | `js_inject` | `str` | — | JavaScript evaluated before capture | | `stealth_mode` | `bool` | `False` | Anti-bot-detection browser fingerprint | | `device_pixel_ratio` | `int` | `1` | Retina/HiDPI scale (1, 2, or 3) | | `timezone` | `str` | Server default | IANA timezone, e.g. `America/New_York` | | `locale` | `str` | Server default | BCP 47 locale, e.g. `en-US` | | `cache_ttl` | `int` | `0` | Cache identical captures for N seconds | | `preload_fonts` | `bool` | `False` | Preload Google Fonts before capture | | `remove_elements` | `list[str]` | — | CSS selectors to remove | | `remove_popups` | `bool` | `False` | Remove common modals/overlays | | `mockup_device` | `"browser" \| "iphone" \| "macbook"` | — | Wrap output in a device frame (PNG) | | `geo_latitude`, `geo_longitude`, `geo_accuracy` | `float` | — | Browser geolocation emulation | ```python result = client.screenshot( { "url": "https://example.com/dashboard", "width": 1280, "height": 720, "device_pixel_ratio": 2, "type": "webp", "quality": 85, "color_scheme": "dark", "wait_until": "networkidle0", "wait_for_selector": ".ready", "delay": 500, "block_ads": True, "remove_cookie_banners": True, "remove_elements": [".newsletter", "#cookie-banner"], "cache_ttl": 3600, "geo_latitude": 40.7128, "geo_longitude": -74.0060, } ) ``` Render HTML & generate PDFs [#render-html--generate-pdfs] Pass `html` to render a raw string — the SDK automatically uses `POST /api/v1/screenshot`. Combine with `type="pdf"` for documents. ```python result = client.screenshot( {"html": "

Invoice #123

", "type": "pdf"} ) with open("invoice.pdf", "wb") as f: f.write(result.image) ``` Error Handling [#error-handling] The SDK raises typed exceptions. All inherit from `ScreenshotAPIError`. ```python from screenshotapi import ( ScreenshotAPI, AuthenticationError, InvalidAPIKeyError, InsufficientCreditsError, ScreenshotFailedError, NetworkError, ScreenshotAPIError, ) client = ScreenshotAPI(os.environ["SCREENSHOTAPI_KEY"]) try: client.screenshot({"url": "https://example.com"}) except AuthenticationError: print("API key missing or malformed (401)") except InvalidAPIKeyError: print("API key invalid or revoked (403)") except InsufficientCreditsError as exc: print(f"Out of credits (402). Balance: {exc.balance}") except ScreenshotFailedError as exc: print(f"Capture failed server-side (500): {exc}") except NetworkError as exc: print(f"Could not reach ScreenshotAPI: {exc}") except ScreenshotAPIError as exc: print(f"ScreenshotAPI error {exc.status} ({exc.code}): {exc}") ``` | Exception | When | | -------------------------- | ------------------------------------------------- | | `AuthenticationError` | `401` — API key missing or malformed | | `InsufficientCreditsError` | `402` — no credits remaining (exposes `.balance`) | | `InvalidAPIKeyError` | `403` — API key invalid or revoked | | `ScreenshotFailedError` | `500` — capture failed server-side | | `NetworkError` | Connection failure or timeout | | `ScreenshotAPIError` | Base class (exposes `.status` and `.code`) | Framework Recipes [#framework-recipes] FastAPI [#fastapi] The async client pairs naturally with FastAPI. ```python from fastapi import FastAPI, HTTPException, Response from screenshotapi import ScreenshotAPI, InsufficientCreditsError app = FastAPI() client = ScreenshotAPI(os.environ["SCREENSHOTAPI_KEY"]) @app.get("/screenshot") async def screenshot(url: str): try: result = await client.async_screenshot({"url": url, "type": "webp"}) except InsufficientCreditsError: raise HTTPException(status_code=402, detail="Out of screenshot credits") return Response(content=result.image, media_type=result.content_type) ``` Django [#django] ```python from django.http import HttpResponse, JsonResponse from screenshotapi import ScreenshotAPI client = ScreenshotAPI(os.environ["SCREENSHOTAPI_KEY"]) def screenshot_view(request): url = request.GET.get("url") if not url: return JsonResponse({"error": "url is required"}, status=400) result = client.screenshot({"url": url, "type": "webp"}) return HttpResponse(result.image, content_type=result.content_type) ``` Flask [#flask] ```python from flask import Flask, request, Response, jsonify from screenshotapi import ScreenshotAPI app = Flask(__name__) client = ScreenshotAPI(os.environ["SCREENSHOTAPI_KEY"]) @app.get("/screenshot") def screenshot(): url = request.args.get("url") if not url: return jsonify(error="url is required"), 400 result = client.screenshot({"url": url, "type": "webp"}) return Response(result.image, mimetype=result.content_type) ``` Runnable versions of these ship in the package's [`examples/`](https://github.com/miketromba/screenshotapi-python/tree/main/examples) directory (`script_usage.py`, `fastapi_app.py`, `django_view.py`, `flask_app.py`). Without the SDK [#without-the-sdk] The endpoint is a single HTTP call, so `requests` works too: ```python import requests response = requests.get( "https://screenshotapi.to/api/v1/screenshot", params={"url": "https://example.com"}, headers={"x-api-key": os.environ["SCREENSHOTAPI_KEY"]}, ) response.raise_for_status() with open("screenshot.png", "wb") as f: f.write(response.content) ``` Next steps [#next-steps] * [Screenshot API reference](/docs/api/screenshot) — every parameter in detail * [Authentication](/docs/authentication) — create and rotate API keys * [Credits](/docs/credits) — how billing works (200 free screenshots/month) * [Integrations](/docs/integrations) — framework and platform guides --- # Ruby (/docs/sdks/ruby) The official **[`screenshotapi_to`](https://rubygems.org/gems/screenshotapi_to)** gem is a small `net/http` client with no runtime dependencies. It captures screenshots, PDFs, and rendered HTML and raises typed errors for API failures. **Gem:** [`screenshotapi_to`](https://rubygems.org/gems/screenshotapi_to) on RubyGems · **Require:** `screenshotapi` · **Source & examples:** [github.com/miketromba/screenshotapi-ruby](https://github.com/miketromba/screenshotapi-ruby) · Ruby 3.0+. Installation [#installation] Add the gem to your Gemfile (note the `require:` so `bundle` loads the right file): ```ruby gem "screenshotapi_to", require: "screenshotapi" ``` ```bash bundle install ``` Or install it directly: ```bash gem install screenshotapi_to ``` Authentication [#authentication] Create an API key in the [dashboard](https://screenshotapi.to/dashboard/api-keys) and keep it in an environment variable. The SDK sends it in the `x-api-key` header. ```bash export SCREENSHOTAPI_KEY="sk_live_your_key_here" ``` ```ruby require "screenshotapi" client = ScreenshotAPI::Client.new(ENV.fetch("SCREENSHOTAPI_KEY")) ``` Keep API keys on the server. Never expose them in client-side code or commit them to source control. Quick Start [#quick-start] Capture a URL and save it to disk [#capture-a-url-and-save-it-to-disk] `save` captures the screenshot, writes the file, and returns the response metadata. ```ruby require "screenshotapi" client = ScreenshotAPI::Client.new(ENV.fetch("SCREENSHOTAPI_KEY")) metadata = client.save(url: "https://example.com", path: "screenshot.png") puts "Screenshot ID: #{metadata.screenshot_id}" puts "Credits remaining: #{metadata.credits_remaining}" ``` Or work with the raw image bytes [#or-work-with-the-raw-image-bytes] `screenshot` returns a `Result` with the image bytes plus metadata. ```ruby result = client.screenshot(url: "https://example.com", type: "webp") File.binwrite("screenshot.webp", result.image) puts result.content_type # "image/webp" puts result.metadata.duration_ms ``` Methods [#methods] `ScreenshotAPI::Client.new(api_key, base_url:, timeout:)` [#screenshotapiclientnewapi_key-base_url-timeout] | Parameter | Type | Default | Description | | ---------- | --------- | -------------------------- | -------------------------------- | | `api_key` | `String` | — (required) | Your API key | | `base_url` | `String` | `https://screenshotapi.to` | API base URL (proxies, tests) | | `timeout` | `Integer` | `60` | Open and read timeout in seconds | `client.screenshot(**options)` [#clientscreenshotoptions] Returns a `ScreenshotAPI::Result`: ```ruby result.image # String (binary) — raw image or PDF bytes result.content_type # "image/png", "image/webp", "application/pdf", … result.metadata.credits_remaining # Integer result.metadata.screenshot_id # String — include this when contacting support result.metadata.duration_ms # Integer ``` `client.save(path:, **options)` [#clientsavepath-options] Captures, writes the bytes to `path:`, and returns a `ScreenshotAPI::Metadata` (the `metadata` shape above). Options [#options] Pass any [screenshot parameter](/docs/api/screenshot) as a snake\_case keyword argument; the client converts them to API parameters for you. | Option | Type | Default | Description | | ----------------------------------------------- | --------------- | ----------------------------- | -------------------------------------------------------------------------- | | `url` | `String` | Required unless `html` is set | URL to capture | | `html` | `String` | — | Raw HTML to render (switches to POST) | | `width` | `Integer` | `1440` | Viewport width in pixels (max 1920) | | `height` | `Integer` | `900` | Viewport height in pixels (max 10000) | | `full_page` | `Boolean` | `false` | Capture the full scrollable page | | `type` | `String` | `"png"` | `"png"`, `"jpeg"`, `"webp"`, or `"pdf"` | | `quality` | `Integer` | `100` | JPEG/WebP quality, 1–100 | | `color_scheme` | `String` | Page default | `"light"` or `"dark"` | | `wait_until` | `String` | `"networkidle2"` | `"load"`, `"domcontentloaded"`, `"networkidle0"`, `"networkidle2"` | | `wait_for_selector` | `String` | — | CSS selector to wait for | | `delay` | `Integer` | `0` | Extra wait after load (ms, max 30000) | | `block_ads` | `Boolean` | `false` | Block common ad networks | | `remove_cookie_banners` | `Boolean` | `false` | Auto-remove cookie consent dialogs | | `css_inject` | `String` | — | CSS injected before capture | | `js_inject` | `String` | — | JavaScript evaluated before capture | | `stealth_mode` | `Boolean` | `false` | Anti-bot-detection browser fingerprint | | `device_pixel_ratio` | `Integer` | `1` | Retina/HiDPI scale (1, 2, or 3) | | `timezone` | `String` | Server default | IANA timezone, e.g. `"America/New_York"` | | `locale` | `String` | Server default | BCP 47 locale, e.g. `"en-US"` | | `cache_ttl` | `Integer` | `0` | Cache identical captures for N seconds | | `preload_fonts` | `Boolean` | `false` | Preload Google Fonts before capture | | `remove_elements` | `Array` | — | CSS selectors to remove | | `remove_popups` | `Boolean` | `false` | Remove common modals/overlays | | `mockup_device` | `String` | — | `"browser"`, `"iphone"`, or `"macbook"` (PNG output) | | `geo_latitude`, `geo_longitude`, `geo_accuracy` | `Numeric` | — | Geolocation override (GET requests) | | `geo_location` | `Hash` | — | Geolocation as `{ latitude:, longitude:, accuracy: }` (HTML/POST requests) | ```ruby result = client.screenshot( url: "https://example.com/pricing", width: 1440, height: 1200, full_page: true, type: "webp", quality: 85, color_scheme: "dark", wait_until: "networkidle2", wait_for_selector: "main", delay: 500, block_ads: true, remove_cookie_banners: true, device_pixel_ratio: 2, cache_ttl: 300, remove_elements: [".newsletter", "#cookie-banner"] ) File.binwrite("pricing.webp", result.image) ``` Render HTML & generate PDFs [#render-html--generate-pdfs] Pass `html:` to render a raw string — the client uses `POST /api/v1/screenshot` with a JSON body. Combine with `type: "pdf"` for documents. ```ruby # Render HTML to PNG result = client.screenshot( html: "

Hello from Ruby

", width: 800, height: 600, type: "png" ) # Save a URL as a PDF client.save(url: "https://example.com/report", type: "pdf", path: "report.pdf") ``` Error Handling [#error-handling] The SDK raises typed errors. All inherit from `ScreenshotAPI::APIError`. ```ruby require "screenshotapi" client = ScreenshotAPI::Client.new(ENV.fetch("SCREENSHOTAPI_KEY")) begin result = client.screenshot(url: "https://example.com") File.binwrite("screenshot.png", result.image) rescue ScreenshotAPI::AuthenticationError warn "API key missing or malformed (401)" rescue ScreenshotAPI::InvalidAPIKeyError warn "API key revoked or invalid (403)" rescue ScreenshotAPI::InsufficientCreditsError => e warn "Out of credits (402). Balance: #{e.balance}" rescue ScreenshotAPI::ScreenshotFailedError => e warn "Capture failed server-side (500): #{e.message}" rescue ScreenshotAPI::NetworkError => e warn "Network error: #{e.message}" rescue ScreenshotAPI::APIError => e warn "ScreenshotAPI error #{e.status}: #{e.message}" end ``` | Error | When | | -------------------------- | ------------------------------------------------- | | `AuthenticationError` | `401` — API key missing or malformed | | `InsufficientCreditsError` | `402` — no credits remaining (exposes `#balance`) | | `InvalidAPIKeyError` | `403` — API key revoked or invalid | | `ScreenshotFailedError` | `500` — capture failed server-side | | `NetworkError` | Connection failure or timeout | | `APIError` | Base class (exposes `#status`) | Framework Recipes [#framework-recipes] Rails Controller [#rails-controller] Stream the screenshot back to the browser and map SDK errors to HTTP responses. ```ruby class ScreenshotsController < ApplicationController def show url = params.require(:url) result = client.screenshot(url: url, type: "webp", quality: 80) response.headers["Cache-Control"] = "public, max-age=3600" send_data result.image, type: result.content_type, disposition: "inline" rescue ScreenshotAPI::InsufficientCreditsError render json: { error: "Out of screenshot credits" }, status: :payment_required rescue ScreenshotAPI::APIError => e render json: { error: e.message }, status: :bad_gateway end private def client @client ||= ScreenshotAPI::Client.new(ENV.fetch("SCREENSHOTAPI_KEY")) end end ``` Runnable examples ship with the gem: [`plain_ruby.rb`](https://github.com/miketromba/screenshotapi-ruby/blob/main/examples/plain_ruby.rb) and [`rails_controller.rb`](https://github.com/miketromba/screenshotapi-ruby/blob/main/examples/rails_controller.rb). Next steps [#next-steps] * [Screenshot API reference](/docs/api/screenshot) — every parameter in detail * [Authentication](/docs/authentication) — create and rotate API keys * [Credits](/docs/credits) — how billing works (200 free screenshots/month) * [Integrations](/docs/integrations) — framework and platform guides --- # Rust (/docs/sdks/rust) There's no official Rust crate yet. The examples below call the [REST API](/docs/api/screenshot) directly with `reqwest` — copy-paste ready. Prefer a native client? Official SDKs ship for [JavaScript](/docs/sdks/javascript), [Python](/docs/sdks/python), [Go](/docs/sdks/go), [Ruby](/docs/sdks/ruby), and [PHP](/docs/sdks/php). Overview [#overview] These examples use [reqwest](https://crates.io/crates/reqwest), the most popular HTTP client for Rust. Add it to your `Cargo.toml`: ```toml [dependencies] reqwest = { version = "0.12", features = ["blocking"] } tokio = { version = "1", features = ["full"] } ``` Quick Start [#quick-start] Set your API key [#set-your-api-key] ```bash export SCREENSHOTAPI_KEY="sk_live_your_key_here" ``` Take a screenshot [#take-a-screenshot] ```rust use std::env; use std::fs; #[tokio::main] async fn main() -> Result<(), Box> { let api_key = env::var("SCREENSHOTAPI_KEY")?; let client = reqwest::Client::new(); let response = client .get("https://screenshotapi.to/api/v1/screenshot") .query(&[("url", "https://example.com")]) .header("x-api-key", &api_key) .send() .await? .error_for_status()?; let credits = response .headers() .get("x-credits-remaining") .and_then(|v| v.to_str().ok()) .unwrap_or("unknown"); println!("Credits remaining: {credits}"); let bytes = response.bytes().await?; fs::write("screenshot.png", &bytes)?; Ok(()) } ``` Client Struct [#client-struct] A reusable client with all screenshot options: ```rust use reqwest::header::HeaderMap; use std::collections::HashMap; pub struct ScreenshotAPI { api_key: String, base_url: String, client: reqwest::Client, } pub struct ScreenshotResult { pub content: Vec, pub content_type: String, pub credits_remaining: i32, pub screenshot_id: String, pub duration_ms: i32, } #[derive(Default)] pub struct CaptureOptions { pub width: Option, pub height: Option, pub full_page: bool, pub format: Option, pub quality: Option, pub color_scheme: Option, pub wait_until: Option, pub wait_for_selector: Option, pub delay: Option, } impl ScreenshotAPI { pub fn new(api_key: impl Into) -> Self { Self { api_key: api_key.into(), base_url: "https://screenshotapi.to".into(), client: reqwest::Client::new(), } } pub async fn capture( &self, url: &str, opts: CaptureOptions, ) -> Result> { let mut params: Vec<(&str, String)> = vec![("url", url.to_string())]; if let Some(w) = opts.width { params.push(("width", w.to_string())); } if let Some(h) = opts.height { params.push(("height", h.to_string())); } if opts.full_page { params.push(("fullPage", "true".into())); } if let Some(ref f) = opts.format { params.push(("type", f.clone())); } if let Some(q) = opts.quality { params.push(("quality", q.to_string())); } if let Some(ref cs) = opts.color_scheme { params.push(("colorScheme", cs.clone())); } if let Some(ref wu) = opts.wait_until { params.push(("waitUntil", wu.clone())); } if let Some(ref ws) = opts.wait_for_selector { params.push(("waitForSelector", ws.clone())); } if let Some(d) = opts.delay { params.push(("delay", d.to_string())); } let response = self .client .get(format!("{}/api/v1/screenshot", self.base_url)) .query(¶ms) .header("x-api-key", &self.api_key) .send() .await? .error_for_status()?; let headers = response.headers().clone(); Ok(ScreenshotResult { content: response.bytes().await?.to_vec(), content_type: header_str(&headers, "content-type") .unwrap_or("image/png".into()), credits_remaining: header_int(&headers, "x-credits-remaining"), screenshot_id: header_str(&headers, "x-screenshot-id") .unwrap_or_default(), duration_ms: header_int(&headers, "x-duration-ms"), }) } } fn header_str(headers: &HeaderMap, name: &str) -> Option { headers.get(name)?.to_str().ok().map(String::from) } fn header_int(headers: &HeaderMap, name: &str) -> i32 { header_str(headers, name) .and_then(|v| v.parse().ok()) .unwrap_or(0) } ``` Usage [#usage] ```rust use std::fs; #[tokio::main] async fn main() -> Result<(), Box> { let api = ScreenshotAPI::new(std::env::var("SCREENSHOTAPI_KEY")?); let result = api.capture("https://github.com", CaptureOptions { width: Some(1280), height: Some(720), format: Some("webp".into()), quality: Some(85), ..Default::default() }).await?; fs::write("github.webp", &result.content)?; println!("Credits remaining: {}", result.credits_remaining); println!("Duration: {}ms", result.duration_ms); Ok(()) } ``` Common Patterns [#common-patterns] Batch Screenshots [#batch-screenshots] Capture multiple URLs concurrently with `tokio::join!`: ```rust use futures::future::join_all; use std::fs; #[tokio::main] async fn main() -> Result<(), Box> { let api = ScreenshotAPI::new(std::env::var("SCREENSHOTAPI_KEY")?); let urls = vec![ "https://example.com", "https://github.com", "https://news.ycombinator.com", ]; let tasks: Vec<_> = urls .iter() .enumerate() .map(|(i, url)| { let api = &api; async move { match api.capture(url, CaptureOptions::default()).await { Ok(result) => { let filename = format!("screenshot-{i}.png"); fs::write(&filename, &result.content).ok(); println!("✓ {url} → {filename}"); } Err(e) => eprintln!("✗ {url}: {e}"), } } }) .collect(); join_all(tasks).await; Ok(()) } ``` Axum Handler [#axum-handler] Serve screenshots in an Axum web application: ```rust use axum::{extract::Query, http::StatusCode, response::IntoResponse}; use serde::Deserialize; #[derive(Deserialize)] struct ScreenshotParams { url: String, } async fn screenshot_handler( Query(params): Query, ) -> Result { let api = ScreenshotAPI::new(std::env::var("SCREENSHOTAPI_KEY").unwrap()); let result = api .capture(¶ms.url, CaptureOptions { format: Some("webp".into()), quality: Some(80), ..Default::default() }) .await .map_err(|_| StatusCode::BAD_GATEWAY)?; Ok(( [(axum::http::header::CONTENT_TYPE, result.content_type), (axum::http::header::CACHE_CONTROL, "public, max-age=3600".into())], result.content, )) } ``` Error Handling [#error-handling] ```rust match api.capture("https://example.com", CaptureOptions::default()).await { Ok(result) => { std::fs::write("screenshot.png", &result.content)?; } Err(e) => { let msg = e.to_string(); if msg.contains("402") { eprintln!("Out of credits — purchase more at screenshotapi.to"); } else if msg.contains("403") { eprintln!("Invalid API key — check your configuration"); } else { eprintln!("Screenshot failed: {msg}"); } } } ```