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.

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:

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

Results carry three 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.

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.

Next Errors