> ## Documentation Index
> Fetch the complete documentation index at: https://docs.context.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Capture Webpage Screenshots

> Get a screenshot PNG of a webpage in a desktop viewport or as a full-page capture

Context.dev's Screenshot API finds and opens any page in a browser, captures a PNG screenshot, hosts it on a CDN, and sends back a URL to access it.

It can dismiss cookie banners, capture the full scrollable height, and find standard pages (like pricing, login, etc.) for a given domain without you entering the exact path.

<Prompt description="Integrate Context.dev's Screenshot API in your app" icon="sparkles" actions={["copy", "cursor"]}>
  I'm integrating Context.dev's Screenshot API (`GET /web/screenshot`) into my app. Help me:

  1. Install the official SDK for my language (`context.dev` on npm / PyPI / RubyGems, `context-dev/context-dev-php` on Packagist, `github.com/context-dot-dev/context-go-sdk` for Go).
  2. Read the API key from the `CONTEXT_DEV_API_KEY` environment variable. Never hardcode it.
  3. Call `client.web.screenshot({ domain })` (or `directUrl` for an exact URL). The response is JSON with `screenshot` (a CDN URL), `screenshotType`, `width`, `height`. It is not raw image bytes.
  4. Toggle `fullScreenshot: "true"` for full-page capture, set `viewport: { width, height }` for a specific viewport, and pass `page: "pricing"` / `"login"` / etc. when I want the heuristic to resolve the right URL on a domain.
  5. Set `handleCookiePopup: "true"` when capturing a site with a consent banner.
  6. Use `maxAgeMs: 0` to force a fresh capture; default cache is 1 day, max 30 days.
  7. Retry HTTP 500 with exponential backoff. Surface HTTP 400 (validation) as a user-facing error.

  Docs: [https://docs.context.dev/guides/take-webpage-screenshot](https://docs.context.dev/guides/take-webpage-screenshot)
</Prompt>

## Prerequisites

* **A Context.dev API key.** Sign up at [context.dev/signup](https://context.dev/signup), copy the key from the [dashboard](https://context.dev/dashboard) (prefix `ctxt_secret_`), and export it:

  ```bash theme={null}
  export CONTEXT_DEV_API_KEY="ctxt_secret_..."
  ```

* **An SDK (optional).** Install for your language, or skip the install and call directly with `curl`:

  <CodeGroup>
    ```bash TypeScript theme={null}
    npm install context.dev
    ```

    ```bash Python theme={null}
    pip install context.dev
    ```

    ```bash Ruby theme={null}
    gem install context.dev
    ```

    ```bash Go theme={null}
    go get github.com/context-dot-dev/context-go-sdk
    ```

    ```bash PHP theme={null}
    composer require context-dev/context-dev-php
    ```
  </CodeGroup>

## Capture a screenshot

There are two ways to point at the page you want to capture:

1. **Give a specific URL**: pass `directUrl` with the full URL (e.g. `https://stripe.com/pricing`).
2. **Give a domain and a page type**: pass `domain` (e.g. `stripe.com`) for the homepage. Add a `page` type (choose from `login`, `signup`, `blog`, `careers`, `pricing`, `terms`, `privacy`, or `contact`) to let Context.dev's resolver find the right URL on the domain.

<CodeGroup>
  ```bash cURL theme={null}
  # 1. Give a specific URL
  curl -G https://api.context.dev/v1/web/screenshot \
    -H "Authorization: Bearer $CONTEXT_DEV_API_KEY" \
    --data-urlencode "directUrl=https://stripe.com/pricing"

  # 2. Give a domain and a page type
  curl -G https://api.context.dev/v1/web/screenshot \
    -H "Authorization: Bearer $CONTEXT_DEV_API_KEY" \
    --data-urlencode "domain=stripe.com" \
    --data-urlencode "page=pricing"
  ```

  ```typescript TypeScript theme={null}
  import ContextDev from "context.dev";

  const client = new ContextDev({ apiKey: process.env.CONTEXT_DEV_API_KEY });

  // 1. Give a specific URL
  const exact = await client.web.screenshot({
    directUrl: "https://stripe.com/pricing",
  });

  // 2. Give a domain and a page type
  const heuristic = await client.web.screenshot({
    domain: "stripe.com",
    page: "pricing",
  });

  console.log(exact.screenshot, heuristic.screenshot);
  ```

  ```python Python theme={null}
  import os
  from context.dev import ContextDev

  client = ContextDev(api_key=os.environ["CONTEXT_DEV_API_KEY"])

  # Give a domain (or `direct_url`) and a page type.
  response = client.web.screenshot(
      domain="stripe.com",
      page="pricing",
  )

  print(response.screenshot)
  ```

  ```ruby Ruby theme={null}
  require "context_dev"

  client = ContextDev::Client.new(api_key: ENV.fetch("CONTEXT_DEV_API_KEY"))

  # 1. Give a specific URL
  exact = client.web.screenshot(
    direct_url: "https://stripe.com/pricing",
  )

  # 2. Give a domain and a page type
  heuristic = client.web.screenshot(
    domain: "stripe.com",
    page: :pricing,
  )

  puts "#{exact.screenshot} #{heuristic.screenshot}"
  ```

  ```go Go theme={null}
  package main

  import (
      "context"
      "fmt"
      "os"

      contextdev "github.com/context-dot-dev/context-go-sdk"
      "github.com/context-dot-dev/context-go-sdk/option"
      "github.com/context-dot-dev/context-go-sdk/packages/param"
  )

  func main() {
      client := contextdev.NewClient(
          option.WithAPIKey(os.Getenv("CONTEXT_DEV_API_KEY")),
      )
      ctx := context.TODO()

      // 1. Give a specific URL
      exact, err := client.Web.Screenshot(ctx, contextdev.WebScreenshotParams{
          DirectURL: param.NewOpt("https://stripe.com/pricing"),
      })
      if err != nil {
          panic(err)
      }

      // 2. Give a domain and a page type
      heuristic, err := client.Web.Screenshot(ctx, contextdev.WebScreenshotParams{
          Domain: param.NewOpt("stripe.com"),
          Page:   contextdev.WebScreenshotParamsPagePricing,
      })
      if err != nil {
          panic(err)
      }

      fmt.Println(exact.Screenshot, heuristic.Screenshot)
  }
  ```

  ```php PHP theme={null}
  <?php

  use ContextDev\Client;

  $client = new Client(apiKey: getenv('CONTEXT_DEV_API_KEY'));

  // 1. Give a specific URL
  $exact = $client->web->screenshot(directURL: 'https://stripe.com/pricing');

  // 2. Give a domain and a page type
  $heuristic = $client->web->screenshot(domain: 'stripe.com', page: 'pricing');

  echo $exact->screenshot, ' ', $heuristic->screenshot, PHP_EOL;
  ```
</CodeGroup>

<Badge color="blue" icon="coins">
  5 credits per successful call
</Badge>

Context.dev finds and opens the page, closes any cookie consent pop-ups (when `handleCookiePopup` is set to `"true"`), takes a screenshot, uploads it to a CDN and returns the public URL.

<Info>
  Captures are cached for one day by default; pass `maxAgeMs: 0` to force a
  fresh render, or up to 30 days for longer reuse.
</Info>

### Request Parameters

<Warning>Pass either `domain` or `directUrl`, not both.</Warning>

| Parameter           | Type                                                                                         | Default                         | Description                                                                                                      |
| ------------------- | -------------------------------------------------------------------------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `domain`            | string                                                                                       | —                               | **Required** unless `directUrl` is provided. Domain to screenshot (e.g. `stripe.com`).                           |
| `directUrl`         | string (URI)                                                                                 | —                               | **Required** unless `domain` is provided. Exact URL to screenshot.                                               |
| `fullScreenshot`    | string enum (`"true"` / `"false"`)                                                           | `"false"`                       | `"true"` captures the full scrollable height; otherwise a single viewport frame.                                 |
| `handleCookiePopup` | string enum (`"true"` / `"false"`)                                                           | `"false"`                       | When `"true"`, dismisses the cookie/consent banner before capture.                                               |
| `viewport`          | object `{ width, height }`                                                                   | `{ width: 1920, height: 1080 }` | Browser viewport in pixels. `width` 240–7680, `height` 240–4320.                                                 |
| `page`              | enum (`login` / `signup` / `blog` / `careers` / `pricing` / `terms` / `privacy` / `contact`) | —                               | Resolve the right URL for the page type via link-scraping heuristics. Only valid with `domain`, not `directUrl`. |
| `maxAgeMs`          | integer                                                                                      | `86400000` (1 day)              | Return a cached capture younger than this. `0` forces fresh. Max `2592000000` (30 days).                         |
| `waitForMs`         | integer                                                                                      | `3000`                          | Browser wait after page load before capture. Min `0`, max `30000` (30 seconds).                                  |
| `timeoutMS`         | integer                                                                                      | —                               | Abort with 408 if the request exceeds this many milliseconds. Min `1000`, max `300000` (5 min).                  |

### Response

```json theme={null}
{
  "status": "ok",
  "code": 200,
  "domain": "stripe.com",
  "screenshot": "https://media.brand.dev/screenshots/cache/f7d1dab4f1fd942032eeb849f1ad9df7.png",
  "screenshotType": "viewport",
  "width": 1920,
  "height": 1080
}
```

| Field            | Type    | Description                                                                                                                                  |
| ---------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`         | string  | `"ok"` on success.                                                                                                                           |
| `code`           | integer | HTTP status code, echoed for convenience.                                                                                                    |
| `domain`         | string  | Normalized domain that was processed. For `directUrl` requests, the host parsed out of the URL.                                              |
| `screenshot`     | string  | Public CDN URL of the captured PNG. Content-addressed: the same query against the same target returns the same URL until `maxAgeMs` expires. |
| `screenshotType` | enum    | `"viewport"` or `"fullPage"`. Mirrors what was actually captured.                                                                            |
| `width`          | integer | Width of the captured image in pixels.                                                                                                       |
| `height`         | integer | Height in pixels. With `fullScreenshot: "true"`, this is the full scrollable height (often 5–20× the viewport).                              |

## Handle errors

The endpoint surfaces four failure modes:

| Status | Meaning                                                                                                                                    | What to do                                                                                                                            |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | Validation error: neither `domain` nor `directUrl` provided, both provided at once, `page` combined with `directUrl`, or a malformed value | Surface as a user-facing error. The response body's `error_code` distinguishes `INPUT_VALIDATION_ERROR`, `WEBSITE_ACCESS_ERROR`, etc. |
| 401    | Missing or invalid API key                                                                                                                 | Re-check the env var and the dashboard.                                                                                               |
| 408    | `REQUEST_TIMEOUT`: the page didn't render before `timeoutMS` elapsed (typically a cold hit)                                                | Retry once with backoff, or prefetch the capture ahead of time.                                                                       |
| 500    | Transient renderer or server error                                                                                                         | Retry with exponential backoff.                                                                                                       |

A retry pattern that covers 500:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import ContextDev from "context.dev";

  const client = new ContextDev({ apiKey: process.env.CONTEXT_DEV_API_KEY });

  async function screenshotWithRetry(domain: string, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
      try {
        return await client.web.screenshot({ domain });
      } catch (err: any) {
        if (err.status === 500 && i < maxRetries - 1) {
          await new Promise((r) => setTimeout(r, 2 ** i * 1000));
          continue;
        }
        throw err;
      }
    }
  }
  ```

  ```python Python theme={null}
  import os
  import time
  from context.dev import ContextDev, APIError

  client = ContextDev(api_key=os.environ["CONTEXT_DEV_API_KEY"])

  def screenshot_with_retry(domain, max_retries=3):
      for i in range(max_retries):
          try:
              return client.web.screenshot(domain=domain)
          except APIError as e:
              if e.status_code == 500 and i < max_retries - 1:
                  time.sleep(2 ** i)
                  continue
              raise
  ```

  ```ruby Ruby theme={null}
  require "context_dev"

  def screenshot_with_retry(domain, max_retries: 3)
    client = ContextDev::Client.new(api_key: ENV.fetch("CONTEXT_DEV_API_KEY"))
    max_retries.times do |i|
      return client.web.screenshot(domain: domain)
    rescue ContextDev::Errors::APIError => e
      raise unless e.status == 500 && i < max_retries - 1
      sleep(2 ** i)
    end
  end
  ```

  ```php PHP theme={null}
  <?php

  use ContextDev\Client;
  use ContextDev\Core\Exceptions\APIStatusException;

  function screenshotWithRetry(string $domain, int $maxRetries = 3) {
      $client = new Client(apiKey: getenv('CONTEXT_DEV_API_KEY'));

      for ($i = 0; $i < $maxRetries; $i++) {
          try {
              return $client->web->screenshot(domain: $domain);
          } catch (APIStatusException $e) {
              if ($e->status === 500 && $i < $maxRetries - 1) {
                  usleep((2 ** $i) * 1_000_000);
                  continue;
              }
              throw $e;
          }
      }
  }
  ```
</CodeGroup>

For the full error code catalog, see [Troubleshooting](/optimization/troubleshooting).

## Use cases

* Auto-generate [on-brand campaign and preview images](/use-cases/branded-campaign-assets) for every page.
* Give your coding agent screenshotting tools to do QA or classification tasks.
* Archive dated visual snapshots of pages for compliance or audits.
* Power server-side link-preview / share-card generation for chat apps, comments, and any paste-a-URL flow.

## Next steps

<CardGroup cols={2}>
  <Card title="Prefetch for Faster Response" icon="bolt" href="/optimization/prefetching">
    Hide cold-hit latency from your users.
  </Card>

  <Card title="Handle Rate Limits" icon="gauge-high" href="/optimization/rate-limits">
    Backoff strategies, client cache, and prefetch fallbacks.
  </Card>

  <Card title="Best Practices" icon="list-check" href="/optimization/best-practices">
    Caching, error handling, and key hygiene.
  </Card>

  <Card title="Troubleshooting" icon="bug" href="/optimization/troubleshooting">
    Status codes, retry patterns, and common errors.
  </Card>
</CardGroup>
