Factuarea API
MCP server

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..-32000 implementation-defined range, or -32603 for internal errors).
  • error.message — a stable string identifier (e.g. insufficient_scope, invoice_cannot_be_modified).
  • error.data.code — the same canonical v1 code the 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 codemessage / data.codeHTTP equiv.Meaning
-32001invalid_token401Missing, malformed or unknown credential; or the user is no longer a member of the company.
-32002client_revoked403The OAuth client was revoked.
-32003invalid_token_type403Wrong credential type for this surface.
-32004plan_limit_exceeded / plan_upgrade_required402A plan usage limit was hit, or the action needs a higher plan. data carries resource, current, limit.
-32005insufficient_scope / module_not_in_plan / feature_flag_disabled403The credential lacks the required scope, the module isn't in the plan, or a feature flag is off. data carries required_scope / module / flag.
-32006rate_limit_exceeded429A throttle bucket was exceeded. data carries retry_after and bucket; the response also sets the Retry-After header.
-32007addon_not_active403The 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.
-32603internal_error500Unexpected 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:

CategoryDefault limitExample tools
read / write60 / minsearch_invoices, create_invoice
send20 / minsend_invoice, send_quote
generate30 / minget_invoice_facturae_link
destructive10 / mindelete_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:

HeaderMeaning
X-RateLimit-Limit-Token / X-RateLimit-Remaining-TokenThe per-token (per-category) bucket.
X-RateLimit-Limit-Hour / X-RateLimit-Remaining-HourThe per-plan hourly bucket (absent on Enterprise).
Retry-AfterOn a 429, seconds to wait before retrying.

Auth endpoint limits

The OAuth endpoints have their own limits, independent of the MCP buckets:

EndpointLimit
POST /api/oauth/register10 / hour per IP
POST /api/oauth/token60 / 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.

On this page