Skip to content

Type an option name like full_page, an error code, or a topic.

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:

Request
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" }
    }
  }'
Response, status 200
{
  "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
The screenshot inside the answer, decoded.

#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.

AddressWhat it is
https://curlshot.com/llms.txtA short map of the docs, with one line per page.
https://curlshot.com/llms-full.txtEvery docs page in one file.
https://curlshot.com/openapi.jsonThe API described in OpenAPI 3.1, a standard format that tools can turn into client code.
Any docs page plus .mdThat page as markdown, for example https://curlshot.com/docs/options.md.
https://curlshot.com/tools.mdWhat the service is used for, one line per task. Each task page has a .md form too.
https://curlshot.com/pricing.mdThe plans, prices and limits, and what counts as a screenshot.
https://curlshot.com/faq.mdThe questions people ask, with short answers.
https://curlshot.com/.well-known/api-catalogA standard file (RFC 9727) that points tools at the API description and the docs.
https://curlshot.com/.well-known/mcp.jsonA 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:

Request
curl "https://curlshot.com/docs/caching" -H "Accept: text/markdown"
Response, first lines
# 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:

MCP client configuration
{
  "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 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:

Request
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" }
    }
  }'
Response, status 200
{
  "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"
  }
}
MethodWhat it doesNeeds a key
initializeSays hello and agrees on a protocol version. The versions 2025-06-18, 2025-03-26 and 2024-11-05 are supported.No
pingAnswers with an empty result.No
tools/listReturns the two tools with the full description of their arguments.No
tools/callRuns 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.

ArgumentRequiredMeaning
urlyesFull address of the page, starting with http:// or https://.
formatnopng, jpeg, webp or pdf.
full_pagenoCapture the whole page, not only the first screen.
viewport_devicenoA device preset such as iphone_15_pro.
viewport_width, viewport_heightnoSize of the browser window in pixels.
dark_modenoAsk the page for its dark theme.
block_adsnoBlock requests to ad networks.
block_cookie_bannersnoHide cookie consent banners.
selectornoCapture only the first element that matches this CSS selector.
wait_untilnoThe page event that counts as "loaded".
delaynoExtra seconds to wait after loading.
cachenoReuse a stored copy of the same request.

The arguments mean the same as the options with the same names in the options reference. Any other argument is refused, and the error text lists the allowed names.

#list_devices

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

Request
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 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:

Response, when the address is wrong
{
  "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. 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:

Response, when the file is over 6 MB
{
  "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.

Arguments for take_screenshot
{
  "url": "https://developer.mozilla.org",
  "format": "webp",
  "cache": true
}

A stored copy is free and comes back faster. See Caching 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. Usage and limits explains how they are counted.

#Where to go next