# Quotas and rate limits

Two separate things bound what you can do: how many searches your plan allows per day, and how fast requests may arrive. Response headers report both, so a client can pace itself without having to provoke an error to find out where the edges are.

## Rate limit: ten requests per minute

The limit is per account and is shared by the API and the [MCP server](https://publicwww.com/docs/mcp/): ten calls in a minute, whichever way they arrive. Searches and all cluster endpoints count, as do the MCP tools `search` and the cluster tools; `/v1/account` and the MCP tools `account` and `read_docs` do not. A refused request counts too, unless it was refused before the rate check: a wrong key, no plan, or a search with no query, a wrong format or column, or no quota left. An eleventh request inside the window comes back **immediately** with `429 too_many_requests` and a `Retry-After` header giving the seconds until a slot frees up. The same number is in the body as `error.retry_after`.

```
HTTP/2 429
Retry-After: 18

{ "error": { "code": "too_many_requests",
"message": "At most 10 requests per minute.",
"retry_after": 18 } }
```

Sleep for `Retry-After` seconds and repeat the request. Nothing was consumed and no quota was spent.

The API never holds a connection open to slow you down. The old export urls do - they sleep a second at a time for up to half a minute before refusing - which is one of the reasons the API exists.

## Daily quota

Your plan allows a number of searches per day. Searches with snippets fall within that allowance and have a smaller daily limit of their own inside it: Pro, for example, allows 100 searches a day, up to 30 of them with snippets. Both reset at the next UTC midnight, not 24 hours after use.

When the daily searches are used up, every search is refused with `429 quota_exceeded`, with snippets or without. When only the snippet limit is used up, searches with snippets are refused with `429 snippet_quota_exceeded`, and ordinary searches still work. Each refusal carries the limit, what has been used, and how long until the reset.

## What costs what

| Request | Costs |
| --- | --- |
| `/v1/search` | One search; with `snippets=1`, also one from the snippet limit. |
| A cluster from a query | One search. |
| A cluster from a list, combine, rename, delete | Nothing. |
| Reading clusters, presets, signatures | Nothing. |
| `/v1/clusters/{id}/extract` | Extraction points: each site found in the index costs as many points as values were found on it, 0.1 if none. |
| `/v1/account` | Nothing, and it does not count against the rate. |

Trying an expression? Start with a small `limit` on extract, say 10: it is the number of sites, and every value found on them is paid for.

## Result depth

A plan also decides how far down the ranking results stay disclosed - `disclosed_positions` from `/v1/account`. Rows past that point are left out rather than blanked, and when any were, `truncated` is `true` in the body and `X-Truncated: true` in the headers.

This is the difference that matters most between the API and the website. A browser whose quota has run out quietly falls back to free-tier depth and shows less, which is fine for a person looking at a page. A script cannot see it happen, so the API refuses instead of shortening.

## Reading the current state

The names are easy to mix up: `X-Requests-*` are the rate, while `X-RateLimit-*` are the daily searches, which is their historic name.

| Header | Meaning |
| --- | --- |
| `X-Requests-Limit` | Requests allowed per minute: 10. |
| `X-Requests-Remaining` | Requests left in this minute. |
| `X-RateLimit-Limit` | Searches allowed today. |
| `X-RateLimit-Remaining` | Searches left today. |
| `X-RateLimit-Reset` | Unix time when the day's quota resets. |
| `X-Snippets-Limit` | Snippet requests allowed today. |
| `X-Snippets-Remaining` | Snippet requests left today. |

- `X-Requests-*` come on the requests that count against the rate, a `429 too_many_requests` included. `/v1/account` has none.
- The other five come with search results, `/v1/account` and answers of the cluster endpoints. Refusals do not reliably carry them: a refused search never does, nor does a `401`, `403 plan_required` or `429 too_many_requests` anywhere. Count on them only on a successful answer.

Search results carry four more:

| Header | Meaning |
| --- | --- |
| `X-Total-Results` | How many sites match in the whole index. |
| `X-Returned-Results` | How many rows this response carries. |
| `X-Truncated` | `true` when the plan's depth limit removed rows. |
| `X-Index-Complete` | `false` when part of the index did not answer in time. |

## Usage statistics

`/v1/account` is the full picture in one call. It spends nothing and does not count against the rate:

```
curl -H "Authorization: Bearer $KEY" https://api.publicwww.com/v1/account
```

```
{
"plan": "enterprise",
"plan_until": 1819461840,
"full_access": true,
"quota": {
"searches": { "limit": 300, "used": 12, "resets_at": 1787961600 },
"snippets": { "limit": 100, "used": 3,  "resets_at": 1787961600 }
},
"limits": {
"disclosed_positions": 4294967295,
"disclosed_positions_snippets": 4294967295,
"max_per_page": 1000000,
"max_per_page_snippets": 10000
},
"clusters": {
"count": 7,
"max": 100,
"max_size": 1000000,
"extract_points_per_day": 1000000,
"extract_points_left": 998166,
"resets_at": "2026-08-29T00:00:00Z"
}
}
```

`resets_at` is the same moment spelled two ways: a unix time under `quota`, an ISO 8601 string under `clusters`.

The older `https://publicwww.com/profile/api_status.xml?key=...` reports the same counters as XML and still works. It belongs to [the old urls](https://publicwww.com/docs/api/legacy/); new code should use `/v1/account`, which also reports the limits, not only the counts.

Source: https://publicwww.com/docs/api/limits/
