# Using the API from AI agents

Let an AI agent take screenshots through the MCP endpoint, and point it at the machine-readable docs, with advice on image size, caching and limits.

An AI agent can ask for a screenshot with one JSON message. This is the raw call that the agent's software sends for you:

```bash
curl -X POST "https://curlshot.com/api/mcp" \
  -H "Authorization: Bearer YOUR_ACCESS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "take_screenshot",
      "arguments": { "url": "https://example.com", "format": "webp" }
    }
  }'
```

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text", "text": "Captured https://example.com, 1280 x 1024 px, 35 KB, image/webp, 87 screenshots left this period. Download: https://curlshot.com/api/v1/files/0d03cca8e56b496b9ae71d02a4d20675.webp?expires=1791155499&token=wLR-uTrZnw9uU18PyJCR2NZS1azZtN_4Y6z98lyhejg (valid until 2026-10-04T23:11:39.000Z)" },
      { "type": "image", "data": "UklGRjaNAABXRUJQVlA4ICqNAAAwZQKdASoABQAEPm02mEmk...", "mimeType": "image/webp" }
    ],
    "isError": false
  }
}
```

The `data` field holds the screenshot, written as base64 text. It is cut short here. A model that can read images sees the page directly.

![The example.com home page](https://curlshot.com/docs/examples/first-screenshot.webp)

## 1. Point the agent at the docs it can read

Agents read plain text better than web pages. Everything on this site is also available in forms made for them.

| Address | What it is |
| --- | --- |
| [`https://curlshot.com/llms.txt`](https://curlshot.com/llms.txt) | A short map of the docs, with one line per page. |
| [`https://curlshot.com/llms-full.txt`](https://curlshot.com/llms-full.txt) | Every docs page in one file. |
| [`https://curlshot.com/openapi.json`](https://curlshot.com/openapi.json) | The API described in OpenAPI 3.1, a standard format that tools can turn into client code. |
| Any docs page plus `.md` | That page as markdown, for example `https://curlshot.com/docs/options.md`. |
| [`https://curlshot.com/tools.md`](https://curlshot.com/tools.md) | What the service is used for, one line per task. Each task page has a `.md` form too. |
| [`https://curlshot.com/pricing.md`](https://curlshot.com/pricing.md) | The plans, prices and limits, and what counts as a screenshot. |
| [`https://curlshot.com/faq.md`](https://curlshot.com/faq.md) | The questions people ask, with short answers. |
| [`https://curlshot.com/.well-known/api-catalog`](https://curlshot.com/.well-known/api-catalog) | A standard file (RFC 9727) that points tools at the API description and the docs. |
| [`https://curlshot.com/.well-known/mcp.json`](https://curlshot.com/.well-known/mcp.json) | A card for the MCP server: its address, how to sign in and its tools. |

You can also ask for markdown with a header. Any docs page answers with markdown when the request says `Accept: text/markdown`:

```bash
curl "https://curlshot.com/docs/caching" -H "Accept: text/markdown"
```

```markdown
# Caching

Store a screenshot once and reuse it for free. How cache, cache_ttl and cache_key work, and how to force a fresh render.
```

## 2. Connect an MCP client

MCP stands for Model Context Protocol. It is a standard way for an AI app to discover and call tools.

Our MCP endpoint is `https://curlshot.com/api/mcp`. It uses the streamable HTTP transport, which means plain `POST` requests that carry JSON-RPC 2.0 messages. JSON-RPC is a small format for "call this method with these values".

The endpoint keeps no session. Every `POST` stands on its own, and there is no stream to open, so a `GET` is answered with status `405`.

Most clients are set up with a JSON file. The exact file name and place differ per client, but the content looks like this:

```json
{
  "mcpServers": {
    "screenshots": {
      "type": "http",
      "url": "https://curlshot.com/api/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_ACCESS_KEY"
      }
    }
  }
}
```

The `Authorization` header carries your access key. Create a separate API key for the agent, so you can revoke it without touching your other keys.

Leave **Require signature** off for that key. Calls through MCP are not signed, so a key that only accepts [signed requests](https://curlshot.com/docs/signed-links.md) answers `signature_required` here.

A client starts by sending `initialize`, then asks for the tools with `tools/list`. You can try the first step by hand:

```bash
curl -X POST "https://curlshot.com/api/mcp" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-06-18",
      "capabilities": {},
      "clientInfo": { "name": "my-agent", "version": "1.0.0" }
    }
  }'
```

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "tools": { "listChanged": false } },
    "serverInfo": { "name": "CurlShot", "title": "CurlShot screenshots", "version": "1.0.0" },
    "instructions": "Use take_screenshot to see what a public web page looks like. It needs the user's CurlShot access key, sent as \"Authorization: Bearer <access key>\". Docs for agents: https://curlshot.com/agent-md"
  }
}
```

| Method | What it does | Needs a key |
| --- | --- | --- |
| `initialize` | Says hello and agrees on a protocol version. The versions `2025-06-18`, `2025-03-26` and `2024-11-05` are supported. | No |
| `ping` | Answers with an empty result. | No |
| `tools/list` | Returns the two tools with the full description of their arguments. | No |
| `tools/call` | Runs a tool. | Only for `take_screenshot` |

You can send up to 20 messages in one `POST`, as a JSON list. The endpoint accepts 120 requests per minute from one IP address. That limit is separate from the limits of your plan.

## 3. Know the two tools

### take_screenshot

Renders a public web page and returns it as an image. Each successful call uses one screenshot from your [quota](https://curlshot.com/docs/usage-and-limits.md).

| Argument | Required | Meaning |
| --- | --- | --- |
| `url` | yes | Full address of the page, starting with `http://` or `https://`. |
| `format` | no | `png`, `jpeg`, `webp` or `pdf`. |
| `full_page` | no | Capture the whole page, not only the first screen. |
| `viewport_device` | no | A device preset such as `iphone_15_pro`. |
| `viewport_width`, `viewport_height` | no | Size of the browser window in pixels. |
| `dark_mode` | no | Ask the page for its dark theme. |
| `block_ads` | no | Block requests to ad networks. |
| `block_cookie_banners` | no | Hide cookie consent banners. |
| `selector` | no | Capture only the first element that matches this CSS selector. |
| `wait_until` | no | The page event that counts as "loaded". |
| `delay` | no | Extra seconds to wait after loading. |
| `cache` | no | Reuse a stored copy of the same request. |

The arguments mean the same as the options with the same names in the [options reference](https://curlshot.com/docs/options.md). Any other argument is refused, and the error text lists the allowed names.

> **Three defaults are different here**
>
> Through MCP, `format` is `jpeg`, and `block_ads` and `block_cookie_banners` are `true`, unless the agent says otherwise. In the normal API, the default format is `png` and both blocking options are `false`. The MCP defaults are chosen to give an agent a small, clean screenshot.

### list_devices

Returns the device presets that `viewport_device` accepts, with their sizes. It takes no arguments, needs no access key and costs nothing.

```bash
curl -X POST "https://curlshot.com/api/mcp" \
  -H "Content-Type: application/json" \
  -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "list_devices", "arguments": {} } }'
```

The answer is a JSON list of devices, the same ones as on the [Devices](https://curlshot.com/docs/devices.md) page. Each entry has a `name` (the value for `viewport_device`), a `label`, a `category`, the `width` and `height` in pixels, the `device_scale_factor`, and `mobile`.

## 4. Read the answer

A successful `take_screenshot` call returns two content parts, as in the example at the top:

- A `text` part with a one-line summary: the address, the size in pixels, the file size, how many screenshots you have left, and a `Download:` link to the stored file with the time it stops working.
- An `image` part with the screenshot as base64 and its `mimeType`.

The download link needs no access key and works for about a day. An agent can pass it on to a person or to another tool.

With `format` set to `pdf`, the second part has the type `resource` and holds the PDF as base64, with the `mimeType` `application/pdf`.

When the call fails, `isError` is `true` and there is a single `text` part that says why:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text", "text": "The screenshot failed (invalid_options: url: must be a full URL starting with http:// or https://)." }
    ],
    "isError": true
  }
}
```

The code inside the brackets is one of the codes on the [errors page](https://curlshot.com/docs/errors.md). An agent can read the sentence and correct itself.

A tool that fails still answers with HTTP status `200`. The failure is in `isError`, the way MCP expects it. Only a message the endpoint cannot read at all gets a JSON-RPC `error` in place of a `result`.

## 5. Keep the images small

Every image an agent looks at takes room in its context, the limited amount of material a model can hold at once. Large images are also slower and cost more to process. So ask for the smallest screenshot that answers the question.

- Prefer `jpeg` or `webp`. They are far smaller than `png` for most pages.
- Use a modest viewport. The default, 1280 by 1024, is fine. For a quick look, `1024` by `768` is enough.
- Leave `full_page` off unless the agent needs the bottom of the page. A long page makes a very large image.
- To inspect one part, pass a `selector`. A screenshot of only the pricing table is smaller and clearer than the whole page.

A file over 6 MB is not put into the answer as base64. The call still succeeds (`isError` is `false`), and the answer is a single `text` part with the summary and the `Download:` link, so the screenshot you paid for is always delivered:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text", "text": "Captured https://example.com, 1280 x 30000 px, 9216 KB, image/png, 86 screenshots left this period. The file is larger than 6 MB, so it is returned as a link instead of inline. For an inline image ask for a smaller viewport, format \"jpeg\", or leave out full_page. Download: https://curlshot.com/api/v1/files/fc8fa16ee40c4e109ca27fc00781dab1.png?expires=1791155499&token=ziDWnqWkJreYpWuBPtqbltnB9YJyt9uO_WapSMBENoc (valid until 2026-10-04T23:11:39.000Z)" }
    ],
    "isError": false
  }
}
```

A model cannot look at a link the way it looks at an inline image. If the agent needs to see the page itself, keep the screenshot small.

## 6. Turn on the cache for repeated looks

Agents often look at the same page several times in one task. With `"cache": true`, the first call renders the page and later calls with the same arguments return the stored copy.

```json
{
  "url": "https://developer.mozilla.org",
  "format": "webp",
  "cache": true
}
```

A stored copy is free and comes back faster. See [Caching](https://curlshot.com/docs/caching.md) for how long a copy stays valid.

Leave the cache off when the agent is checking a change it has made to a page. A stored copy would show the old page.

## 7. Handle limits and errors

Tell your agent what to do when a call fails. Two cases matter most. For anything else, one more try is enough.

**The quota is used up.** The error text contains `quota_exceeded`. In the plain API this is status `402`. Retrying does not help. The agent should stop taking screenshots and tell the user.

**Too many requests.** The error text contains `rate_limited` or `concurrency_limit`, which is status `429` in the plain API. The text ends with "Retry after N seconds." The agent should wait that long and then try once more.

The summary line of every successful call ends with the number of screenshots you have left, so an agent can see the quota shrink before it runs out.

The plan limits themselves are on the [pricing page](https://curlshot.com/pricing). [Usage and limits](https://curlshot.com/docs/usage-and-limits.md) explains how they are counted.

> **Common mistakes**
>
> - **The key is pasted into the prompt.** Prompts end up in logs and transcripts. Put the key in the MCP client configuration or an environment variable.
> - **Every look is a full-page PNG.** That fills the context fast. Use the defaults, or `webp`, and a `selector` where you can.
> - **The agent retries a `quota_exceeded` error in a loop.** Tell it to stop on that code.

## Where to go next

- [Options reference](https://curlshot.com/docs/options.md): what each argument means in detail.
- [Devices](https://curlshot.com/docs/devices.md): the presets that `list_devices` returns.
- [Usage and limits](https://curlshot.com/docs/usage-and-limits.md): quota, rate limit and concurrency.
- [Errors](https://curlshot.com/docs/errors.md): every error code an agent can run into.
