Errors
Structured error shapes returned by the brandRNA REST and MCP APIs.
Every brandRNA error is structured. REST returns a JSON body with HTTP status codes; MCP returns a JSON-RPC error envelope. Your client should never need to scrape error strings — there is always a stable machine- readable code.
REST error shape
All non-2xx REST responses share this shape:
{
"error": "quota_exceeded",
"code": "quota_exceeded",
"details": {
"tier": "free",
"monthly_used": 100,
"monthly_limit": 100,
"upgrade_url": "https://brandrna.com/pricing"
}
}error— short snake_case identifier. Stable across versions.code— alias oferrorfor clients that expect acodefield.details— error-specific structured context. Always an object (may be empty).
Use error / code to branch your retry logic; surface details to
your end-users where appropriate.
4xx vs 5xx semantics
- 4xx — caller's fault. Don't retry without changing the request. 401 / 403 / 404 / 422 / 429 fall here.
- 5xx — our fault. Retry with exponential backoff (start at 500ms, double up to 30s, max 5 retries). 500 / 502 / 504 fall here.
Common errors
| Status | Code | Meaning | Typical cause | Fix |
|---|---|---|---|---|
| 401 | invalid_api_key | Header missing or key not recognised. | Mistyped key, revoked key, missing Bearer. | Re-copy from dashboard. |
| 403 | forbidden_resource | Auth OK but resource is off-limits. | SSRF guard blocked an internal IP, locked feature. | Check the URL is public; contact support if feature-gated. |
| 404 | not_found | Job, domain, or asset doesn't exist. | Wrong job_id, novel domain not yet analysed. | Submit POST /analyze first; poll GET /analyze/{job_id}. |
| 422 | validation_error | Request body malformed. | Bad URL, missing required field. | Inspect details.field for offender. |
| 429 | quota_exceeded | Free-tier monthly cap hit. | Free tier 100 calls/mo or per-IP burst. | Upgrade via details.upgrade_url or wait for the next month. |
| 500 | internal_error | Unexpected server failure. | Bug, transient outage. | Retry with backoff; report on Discord if persistent. |
| 502 | scraper_failure | Playwright crashed mid-scrape. | Cloudflare interstitial, exotic SPA. | Retry; if it persists, the URL likely needs special handling. |
| 504 | scraper_timeout | Scrape exceeded 60s budget. | Slow origin, heavy SPA. | Retry; increase your client timeout to 120s. |
details.upgrade_url is included on 429s so an MCP client (or your own
agent) can deep-link the user straight into the upgrade flow without
hard-coding URLs.
All of the codes above are stable. We may add new codes in future minor versions, but never rename or repurpose existing ones.
MCP error shape
The MCP server speaks JSON-RPC 2.0. Errors travel in the standard envelope:
{
"jsonrpc": "2.0",
"id": "req-42",
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"field": "domain",
"reason": "must be a valid hostname"
}
}
}We use the JSON-RPC standard codes plus a brandRNA-specific block. All
errors are produced via the
McpError factories in app/mcp/errors.py — never hand-rolled — so the
shape is uniform across every tool, resource, and prompt the server
exposes.
MCP error codes
| Code | Name | When |
|---|---|---|
| -32600 | InvalidRequest | Malformed JSON-RPC envelope. |
| -32601 | MethodNotFound | Tool / method not exposed by this MCP server. |
| -32602 | InvalidParams | Tool called with bad params (caught by Zod schema). |
| -32603 | InternalError | Unexpected server failure inside a tool. |
| -32000 | GenericError | Catch-all for brandRNA-specific failures. |
| -32001 | QuotaExceeded | Equivalent of REST 429; data.upgrade_url included. |
| -32002 | UpstreamScraperFail | Playwright failure surfaced via MCP tool call. |
| -32003 | OAuthRevoked | Token was revoked mid-session; re-auth required. |
The data field carries the same structured context as REST details,
so an MCP-aware agent can surface upgrade prompts identically across both
surfaces.
What to do on errors
- 4xx — surface the error to your user; do not auto-retry. The request is wrong, not transient.
- 5xx — exponential backoff. Cap at 5 retries. Beyond that, consider the dependency degraded and switch to your fallback path.
- 429 — show
details.upgrade_urlto the user (or open it in a new tab on click). Don't treat it as a hard error; it's a billing prompt. - OAuthRevoked (-32003) — re-run the OAuth dance; the previous token was rotated or rejected.
For ongoing incidents, watch
https://status.brandrna.com (status page lands in Phase 8). For
real-time triage, our Discord support channel is the fastest route to a
human.
What's next
- Learn how authentication failures surface as 401s.
- Read the rate-limit contract — most 429s trace back here.
- See how MCP clients propagate these errors in the OAuth explainer.