Factuarea APIDevelopers

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 error here — a JSON-RPC error, or an isError result when it is raised inside a tool — with the v1 code and http_status preserved.

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.

Errors raised while a tool runs

A domain error raised while a tool executes — a business rule (422), a missing resource (404), a conflict (409) — does not come back as a JSON-RPC error. The protocol reserves that for protocol failures, so the tool answers with a normal result flagged isError: true, which the model can read and correct:

{
  "jsonrpc": "2.0",
  "id": "<request id>",
  "result": {
    "isError": true,
    "content": [{ "type": "text", "text": "La factura ya emitida no puede modificarse." }],
    "_meta": {
      "com.factuarea/error": {
        "http_status": 422,
        "code": "invoice_cannot_be_modified",
        "hint": "La factura ya emitida no puede modificarse.",
        "param": "status"
      }
    }
  }
}
  • content[0].text is the domain message (the same hint the REST API returns), also in production.
  • _meta["com.factuarea/error"] carries the contract of the REST error: http_status, code, hint and, when they apply, param, subcode and details (for example existing_resource_id of a duplicate). Branch on code, as you do over REST.
  • Only a 5xx is reported as a fault of ours; a rejection is normal use.

The guards that run before the tool — authentication, scopes and plan, throttling, fiscal guardrails, idempotency and the retired tool names — still answer with a JSON-RPC error, as the rest of this page describes.

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 (business mappings use -32099..-32000; protocol errors include -32602 and internal errors use -32603).
  • 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_required402The annual document limit of the plan was reached. For plan_limit_exceeded, data carries resource, current, limit and period_end (full ISO 8601 timestamp with offset), plus http_status, code and hint. plan_upgrade_required keeps its existing meaning: the action needs a higher plan.
-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 has no active Factuarea plan that includes public API access (e.g. an expired trial or lapsed subscription outside its grace period).
-32008(v1 code)422 / 404 / 409 / …A guard that runs before the tool (permissions, fiscal guardrail, idempotency, retired tool names, company selection) rejecting with a business rule, missing resource or conflict. Errors raised inside a tool arrive as a result instead (see above). 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", "contacts: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 / minbulk_invoices_pdf_link, generate_tax_report_303
destructive10 / mindelete_invoice, bulk_archive_contacts

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.

Protocol errors and fiscal guardrails

A protocol lookup can return -32602 when a tool name cannot be resolved; inspect discovery and credential scope before retrying. mcp_guardrail_violation is a 422 business rejection carrying rule_id, guardrail_uri and a subcode. Read the referenced fiscal resource, correct the arguments and follow the tool’s idempotency policy.

On this page

Need a hand?Contact support