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. Both are reported on every response, so a client can pace itself without having to provoke an error to find out where the edges are.
Rate limit: two requests per 30 seconds
A third 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 2 requests per 30 seconds.",
"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 and a number of snippet requests per day, counted separately. Both reset at the next UTC midnight, not 24 hours after use.
- A search costs one from the search allowance.
- A search with
snippets=1costs one from the snippet allowance instead. - Repeating a query you have already run today is not charged again, so paging through a result set costs one search rather than one per page. Snippet requests are charged every time.
/v1/accountcosts nothing.
When an allowance is gone the request is refused with
429 quota_exceeded or 429 snippet_quota_exceeded,
carrying the limit, what has been used, and how long until the reset. Running
out of snippet quota does not stop ordinary searches.
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
Every authenticated response carries five headers:
| Header | Meaning |
|---|---|
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. |
Results carry three 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. |
Usage statistics
/v1/account is the full picture in one call, and it spends nothing:
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
}
}
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.