Guides
Errors
A tool error is a result with isError: true and the problem in the first text block. Branch on its code.
The shape of an error
Link to this section: The shape of an errorThe server reports a failed call as a result, not as a JSON-RPC error. The result has isError: true and the RFC 9457 problem of the REST API as JSON in content[0].. It has no structuredContent: the specification requires structuredContent to match the tool's outputSchema, and an error does not.
{
"isError": true,
"content": [
{
"type": "text",
"text": "{\"type\":\"https://docs.filmo.io/errors/rate_limited\",\"title\":\"Rate limit exceeded\",\"status\":429,\"code\":\"rate_limited\",\"detail\":\"Rate limit exceeded: retry in 17 seconds\",\"requestId\":\"req_…\",\"retryAfter\":17,\"meta\":{\"rateLimit\":{\"limit\":120,\"remaining\":0,\"reset\":17}}}"
}
]
}The problem carries type, title, status, code, detail, requestId and, when they apply, errors[], requiredScope, requiredAddon and retryAfter. A call with an idempotency key also returns meta. inside the problem. Quote requestId when you ask for help. It is the same problem document the REST API returns: see Errors.
Protocol errors
Link to this section: Protocol errorsA malformed request, an unknown method or an unknown tool is a JSON-RPC error with a code such as -32600, -32601, -32602 or -32700, and its message is in English. A call without a credential is not JSON-RPC: it is an HTTP 401 with a problem body. See the protocol reference.
Reading an error with the official SDKs
Link to this section: Reading an error with the official SDKsClient. of the TypeScript SDK and call_tool of the Python SDK return the result; neither raises for a tool error. Check isError and parse the first text block. Only protocol failures, such as an unknown tool or a timeout, raise.
const result = await client.callTool({ name, arguments: args });
if (result.isError) {
const text = (result.content as { type: string; text?: string }[]).find((c) => c.type === "text")?.text ?? "{}";
const problem = JSON.parse(text); // { code, status, detail, requestId, retryAfter?, requiredScope? }
if (problem.code === "rate_limited") await sleep(problem.retryAfter * 1000);
}import asyncio, json
async def call(session, name, args):
result = await session.call_tool(name, arguments=args)
if result.isError:
problem = json.loads(result.content[0].text) # code, status, detail, requestId, retryAfter?, requiredScope?
if problem["code"] == "rate_limited":
await asyncio.sleep(problem["retryAfter"])
return resultIn the 2.x packages the field is is_error in Python and the transport is built differently; the contract of the server is the same. See Build your own client.
Branch on the code
Link to this section: Branch on the code| Code | Status | What to do |
|---|---|---|
unauthorized | 401 | Send a credential. The api_key_* codes say why a key is not valid: revoked, expired or its owner is no longer an administrator. |
insufficient_scope | 403 | The key or connection lacks requiredScope. See below. |
validation_error | 400 | Fix the fields listed in errors[] and call again. |
not_found | 404 | The record does not exist in this account. |
conflict | 409 | Read errors[0]. for the cause; read the record again before retrying. |
precondition_failed | 412 | The record changed since you read it. Read it again. |
idempotency_key_reused | 422 | That key was used with different arguments. Use a new key. |
rate_limited | 429 | Wait retryAfter seconds. |
addon_required | 402 | The account needs the add-on named in requiredAddon. |
service_unavailable | 503 | The feature is switched off for the account; retrying does not help. |
Insufficient scope
Link to this section: Insufficient scopeA tool that needs a scope the connection does not have returns insufficient_scope with requiredScope. The server does not answer with a 403 that asks the client to widen its scopes, so no client adds the scope by itself. To add one, remove the connector and add it again, and check the scope in the consent screen. With a key, create another key with the scope.
The decision table of the REST API applies. A result tells you it is a repetition with meta..
| Status | Idempotency-Key | Idempotent-Replay: true | What to do |
|---|---|---|---|
| 409 conflict · in_progress | Yes | No | errors[0]. is in_progress: another request with the same Idempotency-Key is still running. Wait and retry with the same key (exponential backoff, up to 3 times): you get its stored response when it finishes. If the 409 persists for about two minutes, the reservation is treated as abandoned and the next attempt with the same key runs; read the resource before retrying. |
| 409 conflict · lock_timeout | — | No | errors[0]. is lock_timeout: another write held the resource, or the database aborted the transaction on a transient conflict; nothing was written. Retry with the same key and an exponential backoff, up to 3 times; if it persists, read the resource again. |
| 409 conflict · invalid_state | — | — | errors[0]. is invalid_state: the state of the resource forbids the operation (a unique violation, a line of an atomic batch, an item deleted while saving). Do not retry unchanged: waiting never fixes it. Read the resource again; if the request still fits, you may send it again. |
| 409 contact_in_use · address_in_use · insufficient_stock | — | — | Do not retry. errors[] says what blocks the operation: fix that first. |
| 412 precondition_failed | — | — | Read the resource again, redo your change on top of it and send the new ETag. |
| 422 idempotency_key_reused | Yes | — | A bug in the client: the key was used with a different request (a different method, path or body, compared byte by byte). Use a new key only if it really is a different operation. |
| 429 rate_limited | — | — | Wait the seconds in Retry-After and retry with the same key. |
| 500 internal_error | Yes | Yes | Stop. The failure happened after the commit and is stored and repeated for 24 h with that key: read the resource to see where it ended up. |
| 500 internal_error | Yes | No | Retry with exponential backoff and the same key. |
| 500 internal_error | No | — | Read before retrying a write: without a key nothing protects you from a duplicate. |
| 503 service_unavailable | — | — | Do not retry: the feature is switched off for the account. |
207 (returnOrderLineItems with atomic: false) | Yes | — | The lines that failed were not returned and the rest were. Resend only the failed ones with a new key; repeating the key would return the same 207. |
413 · 502 · 504 (not application) | Yes | — | An answer from the platform, not from the API: decide by the status and retry with the same key. A 413 is not fixed by retrying (the body is too large). |
| Any validation 4xx | — | — | Fix the request and resend it; never in a loop. |
| 401 · 402 · 403 (key, owner or plan) | — | — | Do not retry: resending the same request fails again. Act on the key, its owner or the plan: use a valid key with the right scope, ask an administrator for the missing permission, or add the add-on. |
Every error code has its own page in the REST API tab, with when it happens and what to do.
Related pages
Link to this section: Related pagesGuides
Safe writes
An assistant that can write can also write the wrong thing. Decide which tools need a person, retry without duplicating, and know what a write sets in motion.
Reference
Protocol reference
What a client needs to talk to the server without a ready-made one.
Was this page helpful?