Errors & rate limits
JSON-RPC error shapes mapped from the v1 contract, the full code table, and per-token / per-plan throttling with Retry-After.
The MCP server speaks strict JSON-RPC 2.0. Failures come back as a
error object, never as an HTTP error body the way the REST API does — but the
semantics are identical: the same business-rule violation that returns 422
over REST returns the equivalent JSON-RPC error here, with the v1 code and
http_status preserved in data.
This page covers the MCP-specific JSON-RPC mapping and the MCP throttling
buckets. For the canonical REST contract — the error envelope by code, and the
per-tier quotas — see Errors and
Rate limits.
Error shape
{
"jsonrpc": "2.0",
"id": "<request id>",
"error": {
"code": -32008,
"message": "invoice_cannot_be_modified",
"data": {
"http_status": 422,
"code": "invoice_cannot_be_modified",
"hint": "La factura ya emitida no puede modificarse.",
"param": "status"
}
}
}error.code— the JSON-RPC numeric code (always in the-32099..-32000implementation-defined range, or-32603for internal errors).error.message— a stable string identifier (e.g.insufficient_scope,invoice_cannot_be_modified).error.data.code— the same canonical v1codethe REST API returns, so you can branch on one value across both surfaces.error.data.http_status— the HTTP status the equivalent REST call would return (422 / 404 / 409 / …), for clients that prefer to reason in HTTP terms.error.data.hint— a human-readable message (in Spanish, matching the app's locale). Other fields (param,subcode,required_scope, …) appear when relevant.
Code table
| JSON-RPC code | message / data.code | HTTP equiv. | Meaning |
|---|---|---|---|
-32001 | invalid_token | 401 | Missing, malformed or unknown credential; or the user is no longer a member of the company. |
-32002 | client_revoked | 403 | The OAuth client was revoked. |
-32003 | invalid_token_type | 403 | Wrong credential type for this surface. |
-32004 | plan_limit_exceeded / plan_upgrade_required | 402 | A plan usage limit was hit, or the action needs a higher plan. data carries resource, current, limit. |
-32005 | insufficient_scope / module_not_in_plan / feature_flag_disabled | 403 | The credential lacks the required scope, the module isn't in the plan, or a feature flag is off. data carries required_scope / module / flag. |
-32006 | rate_limit_exceeded | 429 | A throttle bucket was exceeded. data carries retry_after and bucket; the response also sets the Retry-After header. |
-32007 | addon_not_active | 403 | The company's developer API add-on is inactive (outside its grace period). |
-32008 | (v1 code) | 422 / 404 / 409 / … | A business-rule violation, missing resource or conflict. message and data.code are the canonical v1 error code; data.http_status tells you the category. |
-32603 | internal_error | 500 | Unexpected server error. |
-32005 and -32008 each cover several sub-causes. Always branch on
data.code (the string), not only on the numeric code, when you need to tell
them apart — e.g. insufficient_scope vs module_not_in_plan both surface as
-32005.
Insufficient scope
When a tool needs a scope the credential doesn't hold:
{
"jsonrpc": "2.0",
"id": "req-42",
"error": {
"code": -32005,
"message": "insufficient_scope",
"data": {
"http_status": 403,
"code": "insufficient_scope",
"required_scope": "invoices:write",
"provided_scopes": ["invoices:read", "clients:read"],
"hint": "La credencial no tiene el scope requerido para esta operación."
}
}
}Tools your credential can't reach are also hidden from tools/list, so a
well-behaved agent won't normally attempt them — this error is the safety net.
Rate limits
Requests are throttled across three independent buckets. Exceeding any one
returns -32006 with a Retry-After header (seconds).
Per-token, by tool category
Each credential has separate per-minute counters per tool category, so heavy destructive use can't starve your reads:
| Category | Default limit | Example tools |
|---|---|---|
read / write | 60 / min | search_invoices, create_invoice |
send | 20 / min | send_invoice, send_quote |
generate | 30 / min | get_invoice_facturae_link |
destructive | 10 / min | delete_invoice, bulk_delete_clients |
The bucket is resolved from the tool's category. The counter is incremented before the tool runs, so rejected calls (bad scope, validation error) still consume quota — this is deliberate anti-abuse, matching the standard OAuth/REST pattern.
Per-plan, hourly
A company-wide hourly cap by plan slug. The Enterprise plan bypasses this bucket entirely.
Per-OAuth-client, global
OAuth apps additionally share a global per-client bucket of 1000 / min, so a single misbehaving app can't overwhelm the server across all its users.
Rate-limit headers
Successful responses carry the remaining budget so you can back off proactively:
| Header | Meaning |
|---|---|
X-RateLimit-Limit-Token / X-RateLimit-Remaining-Token | The per-token (per-category) bucket. |
X-RateLimit-Limit-Hour / X-RateLimit-Remaining-Hour | The per-plan hourly bucket (absent on Enterprise). |
Retry-After | On a 429, seconds to wait before retrying. |
Auth endpoint limits
The OAuth endpoints have their own limits, independent of the MCP buckets:
| Endpoint | Limit |
|---|---|
POST /api/oauth/register | 10 / hour per IP |
POST /api/oauth/token | 60 / min per (client, IP) |
Always honor Retry-After. Retrying before it elapses keeps the bucket full
and only delays your recovery. Combine it with idempotency on writes so a
delayed retry never duplicates a document.