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

RequestCosts
/v1/searchOne search; with snippets=1, also one from the snippet limit.
A cluster from a queryOne search.
A cluster from a list, combine, rename, deleteNothing.
Reading clusters, presets, signaturesNothing.
/v1/clusters/{id}/extractExtraction points: each site found in the index costs as many points as values were found on it, 0.1 if none.
/v1/accountNothing, 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.

HeaderMeaning
X-Requests-LimitRequests allowed per minute: 10.
X-Requests-RemainingRequests left in this minute.
X-RateLimit-LimitSearches allowed today.
X-RateLimit-RemainingSearches left today.
X-RateLimit-ResetUnix time when the day's quota resets.
X-Snippets-LimitSnippet requests allowed today.
X-Snippets-RemainingSnippet requests left today.

Search results carry four more:

HeaderMeaning
X-Total-ResultsHow many sites match in the whole index.
X-Returned-ResultsHow many rows this response carries.
X-Truncatedtrue when the plan's depth limit removed rows.
X-Index-Completefalse 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; new code should use /v1/account, which also reports the limits, not only the counts.

Next Errors