Skip to content

Rate Limits

The VisibilityIQ API enforces per-endpoint rate limits to protect platform stability. Limits are based on the number of requests per time window, with some endpoints also metered on data rows returned.

EndpointRequests / minuteRow limitNotes
GET /v1/projects60
GET /v1/projects/:id60
GET /v1/projects/:id/audits30
GET /v1/projects/:id/issues301,000/requestUse limit and offset to paginate
GET /v1/backlinks/:target101,000/requestCredit-metered; see below
POST /v1/backlinks/batch525 targets10 credits per target
GET /v1/projects/:id/rankings30500/request

Backlink endpoints are both rate-limited and credit-metered. Each GET /v1/backlinks/:target call costs a base of 10 credits plus 1 credit per row returned. POST /v1/backlinks/batch costs 10 credits per unique target, regardless of limitPerTarget.

Credits are deducted from your monthly data budget. Check your remaining balance under Account → Billing → Data usage.

Every response includes rate limit headers:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1750012345
  • X-RateLimit-Limit — the total requests allowed in the current window
  • X-RateLimit-Remaining — requests remaining in the current window
  • X-RateLimit-Reset — Unix timestamp when the window resets

When you exceed the rate limit, the API returns:

HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json
{
"error": "Rate limit exceeded",
"status": 429
}

The Retry-After header tells you how many seconds to wait before retrying. Do not retry immediately — that will keep returning 429 and waste time.

Recommended handling in code:

async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fetch(url, options);
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get('Retry-After') ?? '60', 10);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
return res;
}
throw new Error('Max retries exceeded');
}
  • Cache responses when the underlying data doesn’t change between requests (audit findings, for example, only change when a new audit runs)
  • Use pagination to fetch large result sets across multiple requests rather than requesting all rows at once
  • For backlink data, batch multiple targets in a single POST /v1/backlinks/batch call rather than multiple individual GET requests
  • Implement exponential backoff with jitter if you’re making high-volume requests