Skip to main content

Errors

Failures arrive through two different channels, and the distinction matters: one is a broken request, the other is something the model should read and react to.

Tool errors​

Everything the caller can act on — validation, not found, missing scope or permission, an endpoint error — comes back as a successful JSON-RPC response whose result is flagged isError, carrying the API's own error envelope as text:

{ "jsonrpc": "2.0", "id": 2, "result": {
"isError": true,
"content": [{ "type": "text",
"text": "{ \"error\": { \"code\": \"NOT_FOUND\", \"message\": \"Payable not found.\" } }" }]
} }

The code vocabulary is the API's, unchanged. The ones you will meet most:

codeWhen
VALIDATION_ERRORThe body failed the endpoint's validation; details.issues[] names each field. Also when the tool's own arguments (id, filters) do not match its schema — including limit, cursor, sort_by or fields inside filters, which are named arguments.
BAD_REQUESTUnknown filter, operator or fields name.
NOT_FOUNDNo such record, or it belongs to another workspace.
CONFLICTA duplicate, or a record still in use.
UNPROCESSABLE_ENTITYA business rule — deleting a category that has children, settling a title with no account.
SECURITY_BLOCKEDThe Security tab blocked the action on this channel.
RATE_LIMITEDThe API budget of this person in the organization ran out — see Rate limit.

And the four MCP adds itself, because they are about authorization, not about the resource:

codeWho fixes it
insufficient_scopeThe token's scope does not reach the tool: the resource is not in the token, write access to it is missing, or the tool moves money and the token does not carry the money resource (or all). Use a token that reaches the resource with the requested access — through the OAuth flow access is read-only today; a personal token can carry write.
insufficient_permissionAn organization admin, granting the permission the error names.
workspace_access_deniedThe workspace_id is not one the person reaches (or does not exist). An admin grants the person the workspace, or the agent passes another one, from finance_workspaces_list.
membership_inactiveAdd the token's person to the organization or reactivate them — no permission fixes a membership that does not exist or is inactive.

They are separate on purpose: four different conversations, with different people. A single error would say "you can't" without saying whom to ask. See Authentication.

There is also workspace_required: the call carried no workspace_id and the connection has no default workspace — the person reaches several. The agent calls finance_workspaces_list and repeats the call with one of the ids. See Authentication → The workspace.

And the ones from confirming destructive tools — in all of them, nothing was changed:

codeWhen
confirmation_requiredA destructive tool called by a client that cannot confirm: 2025, or 2026-07-28 without form elicitation.
USER_DECLINEDOn 2026-07-28, the person declined or cancelled the confirmation.
confirmation_replayedThe resent confirmation already ran the tool once. Call again without requestState to ask anew.
confirmation_unavailableThe server could not guarantee the confirmation at that moment. Try again later.

input_required is not an error​

On 2026-07-28, the first tools/call of a destructive tool may come back with resultType: "input_required" and a confirmation request for the person. That is the normal flow, not a failure: the client shows the question and resends the same call with the answer. See Confirming destructive tools.

Protocol errors​

A malformed message gets a JSON-RPC error object, not a result:

CodeMeaning
-32700Parse error — the body is not valid JSON
-32600Invalid request — not a JSON-RPC object, or an array (batching is not supported)
-32601Method not found
-32602Invalid params — unknown tool, prompt or argument, missing name; on 2026-07-28, also resource not found
-32002Resource not found — only on 2025 (2026-07-28 uses -32602)
-32603Internal error — unexpected failure running the tool
-32020Header mismatch — on 2026-07-28, an MCP-Protocol-Version, Mcp-Method or Mcp-Name header that does not match the body (HTTP 400)
-32022Unsupported protocol version — the requested version is not supported; data.supported lists the supported ones (HTTP 400)

Transport errors​

Refusals that happen before the JSON-RPC message is read are plain HTTP responses: token missing, invalid or issued for another resource, a default workspace (X-Workspace-Id / ?workspace_id=) from another organization, Origin outside the allow-list, protocol rate limit, Kobana Account unavailable to validate the token (503), a body above 14 MB (413, with the JSON-RPC error -32600 Request body too large (limit 14680064 bytes) and id: null). They are listed in Authentication → Failure modes.

Why isError and not a JSON-RPC error​

A 404 on a title is not a defect of the MCP client — it is information about the world, and the model on the other side needs it to correct itself. Burying it in a protocol error would make the client treat it as a transport failure, hide the text and, at best, retry it unchanged. Hence the rule: if the caller can act, it is a result; if the request is broken, it is an error.

Observability​

Every tools/call writes a structured line with the tool, the workspace, the person, the token type (oauth or personal), the status and the duration. Writes are recorded with the mcp channel and the token's person, so what an agent did shows up next to what a person did, on the same screens.