Errors
MisarSEO error body shape, the full error-code table, and how each code maps to an HTTP status.
Every failing api.misar.io/seo/* request returns the same body shape.
{
"error": "Project not found",
"code": "NOT_FOUND"
}errorstringHuman-readable message. Safe to log; not safe to parse.
codestringStable machine-readable code. Branch on this, never on error. Absent on a small number of legacy paths.
Error responses carry Content-Type: application/json and Cache-Control: no-store.
Code to status mapping
| Code | Status | Meaning |
|---|---|---|
UNAUTHENTICATED | 401 | No credentials, or the API key was rejected |
AUTH_CONFIG_MISSING | 401 | The deployment has no usable auth configuration |
FORBIDDEN | 403 | Authenticated but not permitted |
BACKLINKS_NOT_ENABLED | 403 | Backlink data is not enabled for this account |
AI_SEARCH_NOT_ENABLED | 403 | AI search features are not enabled for this account |
CRAWL_TARGET_BLOCKED | 403 | The crawl target is not permitted |
NOT_FOUND | 404 | Project, audit, config, or job does not exist — or belongs to another organization |
CONFLICT | 409 | The resource is in a state that forbids this operation |
VALIDATION_ERROR | 400 | Malformed JSON, a missing required field, or a value out of range |
PAYMENT_REQUIRED | 402 | A paid capability with no active entitlement |
INSUFFICIENT_CREDITS | 402 | Not enough credits for this operation |
BACKLINKS_BILLING_ISSUE | 402 | Backlinks are entitled but billing is blocking |
AI_SEARCH_BILLING_ISSUE | 402 | AI search is entitled but billing is blocking |
PLAN_FEATURE_LOCKED | 402 | The plan does not include this feature |
AUDIT_CAPACITY_REACHED | 429 | Too many concurrent or queued audits |
RATE_LIMITED | 429 | Per-user rate limit exceeded |
PLAN_LIMIT_REACHED | 429 | A plan quota was reached, e.g. tracked-keyword count |
SEO_DATA_UNAVAILABLE | 501 | No honest data source for the requested metric |
UPSTREAM_UNAVAILABLE | 502 | A data provider failed or timed out |
INTERNAL_ERROR | 500 | Unhandled server error |
404 is also how cross-tenant access is refused
Requesting a project, audit, or job belonging to another organization returns 404 NOT_FOUND, not 403. This is deliberate — the API never confirms that another organization's resource id exists.
Success codes worth knowing
Not every success is a 200.
| Code | Where |
|---|---|
201 | POST /seo/projects, POST /seo/keywords/saved, POST /seo/rank-tracking/configs, POST /seo/rank-tracking/keywords, POST /api/keys |
202 | POST /seo/crawl/start, POST /seo/ai/brand-lookup — the work is queued, not done |
Soft failures that return 200
Some endpoints report a problem inside a 200 body rather than with an error status. Check the payload, not just the status.
| Endpoint | Shape |
|---|---|
POST /seo/gsc/performance | { "ok": false, "reason": "…", "connectUrl": "…", "setupDocsUrl": "…" } when Search Console is not connected or the token expired |
POST /seo/gsc/inspect-urls | Same ok: false shape; per-URL failures appear inline as results[].error |
POST /seo/citations | Degrades to citations: [] when the grounded model is unavailable |
Rate limiting
Rate limits are per user, not per IP, and are applied per route with independent windows. Exceeding one returns 429 with a Retry-After header in seconds.
See Rate limits for the per-route numbers.
Handling guidance
Branch on code
Messages are written for humans and change without notice. code is the contract.
Treat 501 as "not here", not "broken"
SEO_DATA_UNAVAILABLE means the deployment has no honest source for that metric. Degrade the feature rather than retrying.
Retry 429 and 502 with backoff
Honour Retry-After on 429. 502 UPSTREAM_UNAVAILABLE is a provider hiccup and is usually worth one retry.
Never retry 400 or 402
A VALIDATION_ERROR will not become valid, and a billing code needs an account change.