Errors and rate limits
The SEOFix API error envelope, every error code with its HTTP status, the rate limits per key and endpoint, insufficient credits, and when to retry.
Every SEOFix API error has the same shape: {"error": {"code": "...", "message": "..."}} with a matching HTTP status. Branch on code, not on message. Each API key may make 60 requests per minute; some endpoints have an extra, lower limit. Retry only 429, 503 and report_not_ready, and honour Retry-After.
The error envelope
{
"error": {
"code": "site_not_verified",
"message": "Rechecks need a verified site. Verify ownership first: see GET /v1/sites/{id}/verification."
}
}
codeis stable and machine-readable.messageis for people and can change.- Validation errors add
fields, the failing parameters with their messages:
{
"error": {
"code": "validation_failed",
"message": "The max rps field must not be greater than 10.",
"fields": {"max_rps": ["The max rps field must not be greater than 10."]}
}
}
budget_exhaustedaddsresets_at(ISO 8601).- Unexpected server errors answer
500 internal_errorand never include internal details.
Over MCP, a failed tool returns the same code as text: code: message, with isError: true.
Error codes
Any endpoint
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 401 | unauthenticated |
No key, a wrong key, or a revoked or rotated key | Send Authorization: Bearer sk_... with a current key |
| 403 | team_access_revoked |
The key holder is no longer a member of the key's team | Issue a new key for a team you belong to |
| 404 | not_found |
Unknown id, unknown path, or a resource of another team | Check the id and the key's team |
| 422 | validation_failed |
A parameter is missing or out of range; see fields |
Fix the request |
| 429 | rate_limited |
A rate limit was hit | Wait for Retry-After seconds |
| 500 | internal_error |
Unexpected server error | Retry later with backoff; email [email protected] if it persists |
Credits and plans
| Status | Code | Meaning |
|---|---|---|
| 402 | insufficient_credits |
The balance is below what the request reserves (the page cap of an audit, or 1 per URL of a recheck) |
| 403 | plan_limit_max_pages |
max_pages is above the plan's per-audit page limit (a quarter of the monthly page tier: at least 10,000 on Starter and 250,000 on Growth, at most 500,000). The message states your limit. |
| 403 | plan_limit_sites |
The plan's site limit is reached |
| 403 | plan_limit_monitoring |
The plan does not include scheduled monitoring |
Audits
| Status | Code | Meaning |
|---|---|---|
| 422 | invalid_url |
The URL is not a public http(s) address |
| 422 | max_rps_requires_verified_site |
max_rps above 3 without a verified site for that origin |
| 422 | site_url_mismatch |
With site_id, the url is not on the site's origin |
| 409 | site_not_verified |
More than 500 pages, rechecks, monitoring, Google data or IndexNow need a verified site |
| 404 | report_not_ready |
The report, diff, templates or fix prompt does not exist yet |
| 410 | crawl_archived |
Issue and page detail of this audit were archived; report, diff and fix prompt remain |
| 409 | conflict |
Cancel on an audit that already ended |
| 503 | dispatch_failed |
The audit could not be queued; its reserved credits are refunded automatically |
Sites
| Status | Code | Meaning |
|---|---|---|
| 409 | site_exists |
The team already has a site for that host |
| 422 | verification_failed |
No ownership proof matched; the message lists what to publish |
Fix tasks and rechecks
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found |
No fix task with this key in the site's latest audit |
| 422 | cannot_verify |
The task's check cannot be judged by a recheck (site-wide, link-graph, sitemap or Google-indexing checks); nothing was started. Run a full audit instead. |
| 422 | invalid_urls |
A URL is not on the site's host, or there are not 1-200 URLs |
| 429 | recheck_rate_limited |
More than 30 rechecks this hour for the team; see Retry-After |
Google Search Console
| Status | Code | Meaning |
|---|---|---|
| 409 | gsc_not_connected |
No active Search Console connection for the site |
| 409 | gsc_forbidden |
The connected Google account lost access to the property |
| 422 | url_not_in_site |
A URL is not part of the site's Search Console property |
| 422 | gsc_request_rejected |
Search Console rejected the request |
| 429 | budget_exhausted |
Today's URL Inspection budget for the property is used up; see resets_at |
| 503 | google_unavailable |
Search Console is temporarily unavailable |
IndexNow
| Status | Code | Meaning |
|---|---|---|
| 409 | indexnow_key_unverified |
The key file has not been verified yet; serve it, then check it |
| 422 | url_not_in_site |
None of the URLs is on the site's host |
| 422 | invalid_indexnow_key |
The key is not 8-128 characters of A-Z a-z 0-9 - |
| 422 | invalid_indexnow_key_location |
key_location is not an https .txt URL on the site's host |
| 429 | indexnow_cap |
The site's 10,000 IndexNow URLs for today (UTC) are used up |
| 502 | indexnow_rejected |
IndexNow accepted none of the URLs, or could not be reached |
Billing
| Status | Code | Meaning |
|---|---|---|
| 403 | not_team_owner |
Only the team owner can buy credits or plans |
| 409 | subscription_exists |
The account already has a subscription; change it in Settings → Plan & billing |
| 409 | no_billing_account |
Nothing bought yet, so there is no billing portal to open |
| 422 | price_not_configured |
That plan, volume and interval is not offered |
| 503 | billing_not_configured |
Billing is not available on this server |
Device login (npx seofix connect)
| Status | Code | Meaning |
|---|---|---|
| 428 | authorization_pending |
Not approved yet; keep polling |
| 429 | slow_down |
Polled faster than every 5 seconds |
| 403 | access_denied |
The request was denied |
| 400 | expired_token |
The code expired (after 10 minutes) |
| 400 | invalid_grant |
Unknown or already used device code |
MCP-only codes
The MCP server adds three codes of its own:
| Code | Meaning |
|---|---|
unauthenticated (No API key provided.) |
The MCP request had no Authorization header; nothing was sent to the API |
network_error |
The MCP server could not reach the API |
http_<status> |
The API answered an error without the usual envelope |
Rate limits
| Scope | Limit | Keyed by |
|---|---|---|
Every authenticated /v1 endpoint, combined |
60 requests per minute | API key |
POST /v1/sites/{id}/verify |
10 per minute | API key |
POST /v1/sites/{id}/google/inspect |
10 per minute | API key |
POST /v1/sites/{id}/indexnow |
10 per minute | API key |
POST /v1/sites/{id}/indexnow/check-key |
10 per minute | API key |
PUT /v1/sites/{id}/indexnow/key |
10 per minute | API key |
POST /v1/sites/{id}/recheck |
30 rechecks per hour | team |
| IndexNow submissions | 10,000 URLs per day (UTC) | site |
| Google URL Inspection | the property's daily budget, shared with the daily sync | Search Console property |
POST /v1/device/start |
10 per hour | IP address |
POST /v1/device/token |
60 per minute, and one poll per 5 seconds per device code | IP address |
GET /v1/health |
none |
The per-endpoint limits apply in addition to the 60 per minute. All keys of one user count separately; the recheck limit is shared by everyone in the team.
Responses carry X-RateLimit-Limit and X-RateLimit-Remaining. A 429 rate_limited answer also carries Retry-After (seconds). recheck_rate_limited and budget_exhausted carry Retry-After too.
Insufficient credits
Audits and rechecks reserve credits before they start:
- An audit reserves its full page cap (
max_pages). The unused part is refunded when it ends. - A recheck reserves 1 credit per URL.
If the balance that pays for it is lower, the API answers 402 insufficient_credits and creates nothing: no audit, no reservation. A recheck refused this way does not count against the hourly recheck limit.
Which balance pays: a plain-URL audit (POST /v1/crawls with url) uses the key holder's own balance; audits of a registered site, scheduled audits and rechecks use the team owner's balance. GET /v1/account shows both (balance and team_credits).
To proceed: lower max_pages to fit the balance, or add credits (Settings → Plan & billing in the app, or a checkout link from POST /v1/billing/checkout for the team owner). Do not retry the same request unchanged.
Retry guidance
| Answer | Retry? |
|---|---|
429 rate_limited, recheck_rate_limited |
Yes, after Retry-After seconds |
429 budget_exhausted |
Yes, after resets_at |
429 indexnow_cap |
Yes, after midnight UTC |
404 report_not_ready |
Yes, every 5-10 seconds while the audit is finalizing or just done |
500 internal_error, 502 indexnow_rejected, 503 codes |
Yes, with exponential backoff (for example 5 s, 15 s, 60 s), a few times |
503 dispatch_failed |
Yes, once after a minute; the failed audit's credits are refunded |
428 authorization_pending |
Yes, every 5 seconds until approved or expired |
400, 401, 402, 403, other 404, 409, 410, 422 |
No: fix the request, the key, the plan or the site first |
Never retry POST /v1/crawls or POST /v1/sites/{id}/recheck after a timeout without checking first: the audit or recheck may have started. List audits with GET /v1/crawls?site_id= before starting another. Over MCP, verify_fix never starts a second recheck on its own; continue with get_recheck.
Related
More in REST API
Still stuck? Email [email protected] with your site and what you expected to see.