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

# Limits and errors

> How many calls a key may make, how much a read returns, and what a refusal looks like in each interface

Ploti limits how fast an API key calls and how much one read returns, so one busy program can't slow everyone else. Every refusal comes with a stable code and a message that says what to do next.

## Rate limits

* **Every call:** 120 a minute per key, over every interface together (the CLI and Python call REST).
* **Heavier calls:** 20 a minute, on top of that, for calls that search widely or start work: `datasets search`, `datasets aggregate`, `datasets estimate`, `datasets profile`, `maps files export`, `requests create`, `requests gaps file`, `webhooks test`, and `feeds subscriptions create`, `update`, `preview` and `manifest`.
* **Maps:** a workspace may make 20 maps an hour.

Over a limit, the answer is `429` `rate_limited` with a `Retry-After` header in seconds. The CLI's downloads wait it out on their own.

## Read limits

| Limit | MCP | CLI, Python and REST |
| - | - | - |
| Rows per page | 1,000 | 5,000 |
| Size per page | 200 KB | 20 MB |
| Rows per lookup | 50 | 50 |
| Groups per aggregate | 1,000 | 1,000 |

* Each read stops after 30 seconds, and a sorted read can page up to 50,000 rows in.
* Each key runs at most 4 reads at once, and reads at most 300,000 rows, 500 MB and 120 seconds of query time a minute.
* For more than a few pages, [download the dataset](https://ploti.ai/docs/developers/download.md).

## Other limits

| What | Limit |
| - | - |
| Webhooks per member | 20 |
| Watched datasets per member | 100 |
| Data Feeds subscriptions per workspace | 25 |
| Request body | 256 KB |

## Errors

A refusal is JSON with a code, a message and links:

```json theme={null}
{
  "error": {
    "code": "outside_coverage",
    "message": "...",
    "links": {}
  }
}
```

Show the message and its link to the person, and don't retry until they have acted on it. A program running on its own should log the code and message and stop that task. The exception is `rate_limited`, which it can retry after `Retry-After` seconds. Codes you will meet:

| Code | Status | Means |
| - | - | - |
| `invalid_input` | 400 | A field is missing or wrong. The message names it, and suggests what to use instead of SQL-style arguments such as `join` or `sql`. |
| `no_subscription` | 402 | The workspace has no active Ploti plan (not a Data Feeds subscription). |
| `insufficient_scope` | 403 | The key lacks a [permission](https://ploti.ai/docs/developers/keys-and-setup.md#what-a-key-may-do) the call needs. |
| `not_allowed` | 403 | Creating, changing or deleting a Data Feeds subscription takes a workspace admin. |
| `member_left` | 403 | The member who made the key has left the workspace. |
| `not_found` | 404 | No dataset, map or request by that id, or one you can't see. |
| `outside_coverage` | 409 | The dataset or place is outside the workspace's coverage areas. |
| `locked` | 403 | The workspace's plan doesn't open that dataset. |
| `stale_cursor` | 409 | The dataset got a new version while you paged. Start the read again. |
| `rate_limited` | 429 | Over a rate limit. Wait `Retry-After` seconds. |

Data Feeds has a few codes of its own, such as `not_subscribed` and `expired`, explained on its pages.

In each interface:

* **CLI:** prints the message and its links and exits with status 1.
* **Python:** raises `ploti.PlotiError` with `code`, `message`, `links` and, for a rate limit, `retry_after` in seconds.
* **REST:** the JSON above, with the HTTP status.
* **MCP:** the tool result is marked as an error, with the same message for the assistant to act on.

```bash CLI theme={null}
ploti datasets rows orange-parcels --columns acreage --place "Travis County, TX"
# ploti: Travis County, TX is outside this workspace's coverage areas. ...
echo $?   # 1
```

```python Python theme={null}
import ploti

try:
    ploti.datasets.rows("orange-parcels", ["acreage"], place="Travis County, TX")
except ploti.PlotiError as error:
    print(error.code, error.message)
```

```bash REST theme={null}
curl -i -X POST https://api.ploti.ai/v1/datasets/orange-parcels/rows \
  -H "Authorization: Bearer $PLOTI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"columns": ["acreage"], "place": "Travis County, TX"}'
```

```json MCP theme={null}
{
  "isError": true,
  "content": [
    {
      "type": "text",
      "text": "Travis County, TX is outside this workspace's coverage areas. ..."
    }
  ]
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.