Errors and limits
Status codes, the error body, and the per-key rate limit.
Status codes
Every failure is a JSON body with one field, error, holding a sentence written for whoever made the call. Branch on the status code; show the sentence to a person.
Status | Meaning | Retry? |
|---|---|---|
| Done. The result is under | — |
| The body was not JSON, or did not match the tool's schema. The message names the field. | No: fix the input |
| No key, or an unknown, revoked or expired one. | No: fix the key |
| The key's role does not allow this tool, or the key may not reach that client. | No |
| No tool by that name. | No |
| Wrong method: tools take | No |
| A well-formed request the product declined — a duplicate name, a plan limit, a spending limit that would be passed — with the reason. | No, unless something changes |
| Rate limited. Wait for | Yes, after the wait |
| The request could not be completed. The reason is never included. | Reads: yes, with backoff. Writes: see below |
Retrying writes
A write that failed with a 500 may already have happened. Repeating create_client after a 500 can create two clients. Retry a 500 freely for tools that only read; for writes, check whether the change landed before trying again. The SDK follows exactly this rule.
A 403 does not tell you whether a client you may not reach exists: tenancy is checked before anything else, so a key pinned to one client learns nothing about the others from the error.
Rate limits
Limits are per key, not per agency: 600 requests a minute, shared between REST and the MCP server. Every response to a recognised key — successes and failures alike — carries:
Header | Meaning |
|---|---|
| Requests allowed per window (600) |
| Requests left in the current window |
| Seconds until the window resets |
A request over the limit is answered 429 with Retry-After in seconds, the same number as X-RateLimit-Reset. A refused request does no work and buys nothing, so retrying it after the wait is always safe. A client that paces itself on X-RateLimit-Remaining never has to be refused to learn when to come back.
The SDK and the command line wait for Retry-After on their own.
Data that is not there
Seolyticize returns null or leaves a field out where it has no measurement — a site that has not been crawled yet, an Analytics property that is not connected, a domain missing from the link graph — rather than a 0. A zero in a response is a measured zero.