Features Full Page Screenshot Wait for Selector & Delay Block Cookie Banners Custom Viewport & Device Website to PDF HTML to Image Dark Mode Image Format & Quality MCP Server Webhook Pricing Docs Blog Log In Sign Up

Screenshots

The Screenshots API is the core of ScreenshotRun. It lets you capture any website, raw HTML, or Markdown as an image or PDF, then retrieve, list, or delete screenshots you have created. All endpoints require authentication.

This page covers every endpoint. For the full list of capture parameters (viewport, format, device emulation, and more), see Screenshot Options.

How It Works

There are two ways to capture a screenshot:

  1. Synchronous (Quick Capture) — send a request and get the image back directly. No polling, no waiting. Best for simple integrations where you need the image right away.
  2. Asynchronous (Create) — send a request and get a screenshot ID back immediately. The screenshot is processed in the background. You can poll for its status or use a webhook to get notified when it is ready. Best for high-volume workloads or when you don't need the image instantly.

How long does a screenshot take?

The API opens a real browser, loads the page, waits for it to render, and captures the image. This means response time depends heavily on the target website. Here is what to expect in practice:

Page typeTypical timeExamples
Simple / static HTML1–2 sLanding pages, documentation, text-heavy sites
Medium complexity2–5 sNews sites, GitHub, Wikipedia
JS-heavy / SPA5–8 sStripe, Vercel, React/Vue apps, Shopify
Full-page, long contentUp to 10+ sLong Wikipedia articles, full documentation pages

These are real numbers measured from our servers. The processing_time_ms field in every response tells you exactly how long the browser took for that specific capture.

Quick decision guide: sync vs. async

Not sure which mode to use? Pick based on your situation:

SituationRecommended mode
Building a tool that generates one screenshot on user actionSync (/capture)
Embedding a screenshot directly in an <img> tag or PDFSync (/capture)
Capturing many URLs in a loop or cron jobAsync (POST /screenshots)
Monitoring dozens or hundreds of pagesAsync + webhooks
Your backend cannot wait more than a few seconds for a responseAsync (POST /screenshots)
You are capturing complex, JS-heavy sitesAsync (more reliable, no HTTP timeout risk)

Quick Capture

http
GET /v1/screenshots/capture
POST /v1/screenshots/capture

The simplest way to take a screenshot. The API waits for the capture to finish and returns the image file directly — no polling needed.

Use GET with query parameters for simple requests. Use POST with a JSON body when sending large payloads like html or markdown (GET query strings have length limits).

Both methods accept the same parameters.

Set your HTTP client timeout to at least 30 seconds

Most HTTP libraries default to a 5–10 second timeout. Because /capture holds the connection open while the browser loads the page, a short timeout in your HTTP client will cause the request to fail with a connection error — even though our server is working fine.

Set your client timeout to 30–60 seconds when using the sync endpoint. If you cannot change the timeout in your environment, use async mode instead.

Setting the HTTP client timeout

Here is how to configure it in the most common languages:

JavaScript (fetch)

javascript
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 60_000); // 60 seconds

const res = await fetch("https://api.screenshotrun.com/v1/screenshots/capture?url=https://example.com", {
  headers: { "Authorization": "Bearer YOUR_API_KEY" },
  signal: controller.signal,
});
clearTimeout(timeout);

JavaScript (axios)

javascript
const res = await axios.get("https://api.screenshotrun.com/v1/screenshots/capture", {
  params: { url: "https://example.com" },
  headers: { Authorization: "Bearer YOUR_API_KEY" },
  timeout: 60_000, // 60 seconds
  responseType: "arraybuffer", // for binary image response
});

Python (requests)

python
import requests

response = requests.get(
    "https://api.screenshotrun.com/v1/screenshots/capture",
    params={"url": "https://example.com"},
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    timeout=60,  # seconds
    stream=True,
)

with open("screenshot.png", "wb") as f:
    f.write(response.content)

PHP (Guzzle / Laravel HTTP)

php
// Laravel HTTP client
$response = Http::withToken(env('SCREENSHOT_API_KEY'))
    ->timeout(60) // seconds
    ->get('https://api.screenshotrun.com/v1/screenshots/capture', [
        'url' => 'https://example.com',
    ]);

file_put_contents('screenshot.png', $response->body());

// Guzzle directly
$client = new \GuzzleHttp\Client(['timeout' => 60]);
$response = $client->get('https://api.screenshotrun.com/v1/screenshots/capture', [
    'query'   => ['url' => 'https://example.com'],
    'headers' => ['Authorization' => 'Bearer YOUR_API_KEY'],
    'sink'    => 'screenshot.png',
]);

Ruby (Net::HTTP)

ruby
require "net/http"
require "uri"

uri = URI("https://api.screenshotrun.com/v1/screenshots/capture?url=https://example.com")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 60  # seconds
http.open_timeout = 10  # seconds

request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"

response = http.request(request)
File.binwrite("screenshot.png", response.body)

Go

go
client := &http.Client{Timeout: 60 * time.Second}

req, _ := http.NewRequest("GET",
    "https://api.screenshotrun.com/v1/screenshots/capture?url=https://example.com", nil)
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")

resp, err := client.Do(req)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

data, _ := io.ReadAll(resp.Body)
os.WriteFile("screenshot.png", data, 0644)

Example: GET with query parameters

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.screenshotrun.com/v1/screenshots/capture?url=https://example.com&width=1440&format=webp" \
  -o screenshot.webp

Example: POST with JSON body

bash
curl -X POST https://api.screenshotrun.com/v1/screenshots/capture \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<h1>Hello World</h1><p>Rendered from HTML</p>",
    "width": 1200,
    "height": 630
  }' \
  -o screenshot.png

Using in the browser

Because the GET endpoint returns a binary image, you can use it directly in HTML as a link or an image source.

html
<a href="https://api.screenshotrun.com/v1/screenshots/capture?url=https://example.com&width=1280&format=png">
  Take Screenshot
</a>
Never expose a full-access API key in client-side code

Anyone who views your page source can copy an API key embedded in HTML or JavaScript and use it from their own server. To safely use the API in the browser, create a domain-restricted key:

  1. Go to Dashboard → API Keys → Create New API Key.
  2. In the Allowed Domains field, enter the domain your site runs on — for example myapp.com. One domain per line.
  3. Use that key in your client-side code.

A domain-restricted key is rejected by the API if the request comes from any other origin, so even if someone copies the key, they cannot use it from their own site or server.

Tip

To get the async JSON response instead (same behavior as POST /v1/screenshots), pass response_type=json.

Create a Screenshot

http
POST /v1/screenshots

Queues a new screenshot for capture in the background. Returns a 202 Accepted response with the screenshot object in pending status. This is the async method — the screenshot is not ready yet when you get the response.

After creating a screenshot, you can either poll for its status with Get a Screenshot, or set up a webhook to be notified automatically when it completes.

When to use async vs. sync

  • Use async (POST /screenshots) when you are capturing many screenshots, processing them in a queue, or want non-blocking behavior in your application.
  • Use sync (GET /capture) when you need the image right away and your use case is simple — a single capture at a time.

The async approach has one key advantage: your application does not hold an open connection while the browser is running. You fire the request, get an ID, and your server is free to do other work. This makes async a better fit for any situation where you are capturing more than a handful of URLs, or where your hosting environment has strict request timeout limits (serverless functions, shared hosting, etc.).

Example Request

cURL

bash
curl -X POST https://api.screenshotrun.com/v1/screenshots \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "width": 1440,
    "height": 900,
    "format": "webp",
    "full_page": true,
    "dark_mode": true
  }'

PHP

php
$response = Http::withToken(env('SCREENSHOT_API_KEY'))
    ->post('https://api.screenshotrun.com/v1/screenshots', [
        'url' => 'https://example.com',
        'width' => 1440,
        'height' => 900,
        'format' => 'webp',
        'full_page' => true,
        'dark_mode' => true,
    ]);

$screenshot = $response->json('data');
echo "Screenshot ID: " . $screenshot['id'];

Python

python
import requests

response = requests.post(
    "https://api.screenshotrun.com/v1/screenshots",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "url": "https://example.com",
        "width": 1440,
        "height": 900,
        "format": "webp",
        "full_page": True,
        "dark_mode": True,
    }
)

screenshot = response.json()["data"]
print(f"Screenshot ID: {screenshot['id']}")

JavaScript (Node.js)

javascript
const response = await fetch("https://api.screenshotrun.com/v1/screenshots", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://example.com",
    width: 1440,
    height: 900,
    format: "webp",
    full_page: true,
    dark_mode: true,
  }),
});

const { data } = await response.json();
console.log(`Screenshot ID: ${data.id}`);

Ruby

ruby
require "net/http"
require "json"
require "uri"

uri = URI("https://api.screenshotrun.com/v1/screenshots")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
request["Content-Type"] = "application/json"
request.body = {
  url: "https://example.com",
  width: 1440,
  height: 900,
  format: "webp",
  full_page: true,
  dark_mode: true
}.to_json

response = http.request(request)
screenshot = JSON.parse(response.body)["data"]
puts "Screenshot ID: #{screenshot['id']}"

Go

go
payload := strings.NewReader(`{
  "url": "https://example.com",
  "width": 1440,
  "height": 900,
  "format": "webp",
  "full_page": true,
  "dark_mode": true
}`)

req, _ := http.NewRequest("POST", "https://api.screenshotrun.com/v1/screenshots", payload)
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
req.Header.Set("Content-Type", "application/json")

resp, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

var result struct {
    Data struct {
        ID string `json:"id"`
    } `json:"data"`
}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("Screenshot ID: %s\n", result.Data.ID)

Response (202 Accepted)

json
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "pending",
    "url": "https://example.com",
    "options": {
      "width": 1440,
      "height": 900,
      "format": "webp",
      "quality": 80,
      "full_page": true,
      "device": "desktop",
      "block_ads": false,
      "block_cookies": true,
      "dark_mode": true,
      "delay": 0,
      "timeout": 30,
      "retina": false
    },
    "estimated_time": 5,
    "created_at": "2026-03-09T10:30:00.000000Z",
    "links": {
      "self": "https://api.screenshotrun.com/v1/screenshots/550e8400-e29b-41d4-a716-446655440000"
    }
  }
}

Screenshot Statuses

After you create a screenshot, it moves through these statuses:

StatusMeaning
pendingThe screenshot is queued and waiting to be processed.
processingA browser is loading the page and taking the screenshot right now.
completedThe screenshot is ready. You can download the image.
failedSomething went wrong. Check the error field for details.

Polling for Completion

If you are not using webhooks, you can poll for the screenshot status. Here is a simple polling example in Python:

python
import time
import requests

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.screenshotrun.com/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}

# Create screenshot
response = requests.post(
    f"{BASE_URL}/screenshots",
    headers=headers,
    json={"url": "https://example.com"}
)
screenshot_id = response.json()["data"]["id"]

# Poll until ready
while True:
    status_response = requests.get(
        f"{BASE_URL}/screenshots/{screenshot_id}",
        headers=headers,
    )
    data = status_response.json()["data"]

    if data["status"] == "completed":
        # Download the image
        image_response = requests.get(
            f"{BASE_URL}/screenshots/{screenshot_id}/image",
            headers=headers,
        )
        with open("screenshot.png", "wb") as f:
            f.write(image_response.content)
        print("Screenshot saved!")
        break
    elif data["status"] == "failed":
        print(f"Failed: {data['error']['message']}")
        break

    time.sleep(2)  # Wait 2 seconds before next poll
Tip

For production workloads, use webhooks instead of polling. They are more efficient and give you instant notifications.

Async mode in practice: capturing multiple URLs

A common pattern is to kick off several screenshots at once, then collect the results as they finish. This is much faster than calling /capture one by one, because all the browser sessions run in parallel on our servers.

JavaScript (Node.js)

javascript
const API_KEY = "YOUR_API_KEY";
const BASE    = "https://api.screenshotrun.com/v1";
const urls    = [
  "https://example.com",
  "https://github.com",
  "https://stripe.com",
];

// 1. Kick off all screenshots at once
const screenshots = await Promise.all(
  urls.map(url =>
    fetch(`${BASE}/screenshots`, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ url, format: "jpeg", width: 1280 }),
    })
    .then(r => r.json())
    .then(r => r.data)
  )
);

console.log("Created:", screenshots.map(s => s.id));

// 2. Poll until all are done (simple version — use webhooks in production)
async function waitForAll(ids) {
  const results = {};
  const pending = new Set(ids);

  while (pending.size > 0) {
    await new Promise(r => setTimeout(r, 2000)); // wait 2s between checks

    for (const id of [...pending]) {
      const res  = await fetch(`${BASE}/screenshots/${id}`, {
        headers: { "Authorization": `Bearer ${API_KEY}` },
      });
      const data = (await res.json()).data;

      if (data.status === "completed") {
        results[id] = data.links.image;
        pending.delete(id);
        console.log(`Done: ${id} → ${data.links.image}`);
      } else if (data.status === "failed") {
        results[id] = null;
        pending.delete(id);
        console.error(`Failed: ${id} — ${data.error?.message}`);
      }
    }
  }
  return results;
}

const results = await waitForAll(screenshots.map(s => s.id));
console.log("All done:", results);

Python

python
import time
import requests

API_KEY = "YOUR_API_KEY"
BASE    = "https://api.screenshotrun.com/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}

urls = [
    "https://example.com",
    "https://github.com",
    "https://stripe.com",
]

# 1. Kick off all screenshots at once
ids = []
for url in urls:
    resp = requests.post(
        f"{BASE}/screenshots",
        headers=headers,
        json={"url": url, "format": "jpeg", "width": 1280},
    )
    screenshot_id = resp.json()["data"]["id"]
    ids.append(screenshot_id)
    print(f"Created: {screenshot_id} for {url}")

# 2. Poll until all are done
pending = set(ids)
results = {}

while pending:
    time.sleep(2)  # wait 2 seconds between checks

    for sid in list(pending):
        data = requests.get(f"{BASE}/screenshots/{sid}", headers=headers).json()["data"]

        if data["status"] == "completed":
            results[sid] = data["links"]["image"]
            pending.discard(sid)
            print(f"Done: {sid} → {data['links']['image']}")
        elif data["status"] == "failed":
            results[sid] = None
            pending.discard(sid)
            print(f"Failed: {sid} — {data.get('error', {}).get('message')}")

print("All done:", results)
Polling interval

Most screenshots complete in 2–8 seconds. Polling every 2 seconds is a reasonable starting point. Do not poll faster than once per second — it wastes your rate limit budget without meaningful benefit. For production systems with many screenshots, use webhooks to eliminate polling entirely.

Using the timeout parameter

The timeout parameter controls how long the browser will wait for a page to load before giving up. It defaults to 30 seconds and accepts values from 5 to 60 seconds.

This is different from your HTTP client timeout. The relationship between the two is:

  • API timeout — how long our browser waits for the page to load. If the page does not respond in time, we return a CAPTURE_FAILED error.
  • HTTP client timeout — how long your code waits for our server to respond. This should always be longer than the API timeout.

A safe rule: set your HTTP client timeout to API timeout + 15 seconds. If you use timeout=30 (the default), set your HTTP client timeout to at least 45 seconds.

json
{
  "url": "https://example.com",
  "timeout": 45,
  "format": "jpeg"
}
Slow sites

If you are capturing sites that are known to be slow (large e-commerce catalogs, sites with heavy third-party scripts), increase timeout to 45–60 and switch to async mode. Async mode removes the HTTP connection timeout problem entirely, since your request returns in milliseconds with a screenshot ID.

Get a Screenshot

http
GET /v1/screenshots/{id}

Returns the screenshot object with its current status and details. Use this to check whether an async screenshot has finished processing.

Example Request

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://api.screenshotrun.com/v1/screenshots/550e8400-e29b-41d4-a716-446655440000

Response — Completed

When the screenshot is ready, the response includes full metadata: image dimensions, file size, processing time, and download links.

json
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "url": "https://example.com",
    "options": {
      "width": 1440,
      "height": 900,
      "format": "webp",
      "quality": 80,
      "full_page": true,
      "device": "desktop",
      "block_ads": false,
      "block_cookies": true,
      "dark_mode": true,
      "delay": 0,
      "timeout": 30,
      "retina": false
    },
    "file_size": 245760,
    "mime_type": "image/webp",
    "width": 1440,
    "height": 3200,
    "processing_time_ms": 3450,
    "completed_at": "2026-03-09T10:30:05.000000Z",
    "expires_at": "2026-04-09T10:30:05.000000Z",
    "created_at": "2026-03-09T10:30:00.000000Z",
    "links": {
      "self": "https://api.screenshotrun.com/v1/screenshots/550e8400-e29b-41d4-a716-446655440000",
      "image": "https://api.screenshotrun.com/v1/screenshots/550e8400-e29b-41d4-a716-446655440000/image"
    }
  }
}

Response — Failed

If something went wrong during capture, the error field tells you what happened. Common causes include page timeouts, invalid URLs, and blocked content.

json
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "failed",
    "url": "https://example.com",
    "options": { ... },
    "error": {
      "message": "Page load timed out after 30 seconds.",
      "code": "TIMEOUT"
    },
    "created_at": "2026-03-09T10:30:00.000000Z",
    "links": {
      "self": "https://api.screenshotrun.com/v1/screenshots/550e8400-e29b-41d4-a716-446655440000"
    }
  }
}

Get Screenshot Image

http
GET /v1/screenshots/{id}/image

Downloads the actual image file. This only works for screenshots with completed status. The response includes the correct Content-Type header for the format you chose (e.g. image/png, image/webp, application/pdf).

Example Request

bash
# Download to file
curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://api.screenshotrun.com/v1/screenshots/550e8400-.../image \
  -o screenshot.webp

Responses

StatusDescription
200 OKImage file with appropriate Content-Type header (image/png, image/jpeg, image/webp, image/avif, image/tiff, or application/pdf)
404 Not FoundScreenshot is not ready yet (status is pending or processing)
410 GoneScreenshot image has expired and been deleted (see Image Retention below)

Image Retention

Screenshots are stored temporarily. After the retention period expires, the image file is automatically deleted. The metadata (status, options, timestamps) stays available through the API, but the image itself returns 410 Gone.

How long your screenshots are kept depends on your plan:

PlanRetention
Free24 hours
Starter48 hours
Pro7 days
Growth30 days
Business30 days
Note

Download images as soon as they are ready, or use webhooks to trigger automatic downloads when screenshots complete. You can also create signed URLs to share images without exposing your API key.

List Screenshots

http
GET /v1/screenshots

Returns a paginated list of your screenshots, newest first. You can filter by status and date range, which is useful for building dashboards or audit logs.

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number
per_pageinteger20Results per page (max 100)
statusstring—Filter by status: pending, processing, completed, failed
sort_bystringcreated_atSort field
sort_dirstringdescSort direction: asc or desc
date_fromstring—Filter screenshots created after this date (ISO 8601)
date_tostring—Filter screenshots created before this date (ISO 8601)

Example Request

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.screenshotrun.com/v1/screenshots?status=completed&per_page=10&sort_dir=desc"

Response

json
{
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "completed",
      "url": "https://example.com",
      "options": { ... },
      "file_size": 245760,
      "mime_type": "image/webp",
      "width": 1440,
      "height": 3200,
      "processing_time_ms": 3450,
      "completed_at": "2026-03-09T10:30:05.000000Z",
      "expires_at": "2026-04-09T10:30:05.000000Z",
      "created_at": "2026-03-09T10:30:00.000000Z",
      "links": {
        "self": "...",
        "image": "..."
      }
    }
  ],
  "links": {
    "first": "https://api.screenshotrun.com/v1/screenshots?page=1",
    "last": "https://api.screenshotrun.com/v1/screenshots?page=5",
    "prev": null,
    "next": "https://api.screenshotrun.com/v1/screenshots?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 5,
    "links": [
      {"url": null, "label": "« Previous", "page": null, "active": false},
      {"url": "...?page=1", "label": "1", "page": 1, "active": true},
      {"url": "...?page=2", "label": "2", "page": 2, "active": false},
      {"url": "...?page=2", "label": "Next »", "page": 2, "active": false}
    ],
    "path": "https://api.screenshotrun.com/v1/screenshots",
    "per_page": 10,
    "to": 10,
    "total": 48
  }
}

Delete a Screenshot

http
DELETE /v1/screenshots/{id}

Permanently deletes a screenshot and its image file. This action cannot be undone.

Example Request

bash
curl -X DELETE -H "Authorization: Bearer YOUR_API_KEY" \
  https://api.screenshotrun.com/v1/screenshots/550e8400-e29b-41d4-a716-446655440000

Response

Returns 204 No Content on success with an empty body.

Feature Availability

Some parameters are only available on certain plans. If you use a feature not included in your plan, the API returns a 422 error telling you which feature requires an upgrade. Check your current plan details anytime via GET /v1/account.

Feature / ParameterFreeStarterProBusiness
url, width, heightYesYesYesYes
full_pageYesYesYesYes
delayYesYesYesYes
block_cookiesYesYesYesYes
selectorYesYesYesYes
wait_for_selectorYesYesYesYes
omit_backgroundYesYesYesYes
reduced_motionYesYesYesYes
device (mobile/tablet)—YesYesYes
retina—YesYesYes
block_ads—YesYesYes
block_chats—YesYesYes
format: pdf—YesYesYes
webhook_url—YesYesYes
html (HTML rendering)—YesYesYes
user_agent—YesYesYes
headers, cookies—YesYesYes
dark_mode——YesYes
css, js——YesYes
stealth——YesYes
timezone——YesYes
extract_metadata——YesYes
markdown (Markdown rendering)——YesYes
Batch API—YesYesYes
Signed URLs——YesYes
PDF options (landscape, page size, margins)—YesYesYes
geolocation———Yes
proxy———Yes
scroll_video——YesYes

Next Steps

  • Screenshot Options — full parameter reference, grouped by category, with examples for every option
  • Caching — avoid redundant captures by caching screenshots
  • Signed URLs — share screenshots without exposing your API key
  • Batch Screenshots — capture up to 100 URLs in a single request
  • Webhooks — get notified when screenshots are ready