Skip to main content
A failure inside a tool is not a transport error. The HTTP status stays 200, and the JSON-RPC result is a normal tool result with isError: true. content[0].text reads <code>: <detail> <recovery>. structuredContent.error carries the problem body the REST API returns for the same failure. It also carries a recovery sentence and any facts that save a round trip. For example, godmode_deals_summary at a point in New York:
The codes a tool can return. The last column is the recovery sentence itself, word for word. So what a client shows a reader is what the code carries: Two details about errors[]. The tool encodes the arguments to the /v1 querystring before the route’s validator runs. So a geometry failure names the wire field, not the tool’s input field. A lone latitude comes back as geometry_conflict with param: "lng" and the detail “lat and lng must be given together”. The tool’s schema does not refuse an argument it does not define. The tool encodes it, and the route refuses it as unknown_parameter, which is how a bracketed list reads. godmode_coverage is the exception: it builds no wire query and validates none. So it drops an argument it does not define without a word. Three real bodies, trimmed to the parts that differ:
Ambiguity is not an error. An APN that names distinct parcels, and a name that matches several entities, come back as a success with resolved: false and candidates[]. Rate limits and credits. The gateway enforces the plan’s requests a minute and a day. So a burst answers 429 with the problem body (code: "rate_limited", request_id) and a Retry-After header, and the gateway never reads the JSON-RPC message. A key whose plan ended answers 403 subscription_required, and a key whose card failed more than seven days ago answers 403 payment_overdue, the same way. A tools/call costs what its route costs (Plans and limits). On a client that negotiates protocol 2026-07-28, that cost is the ledger’s figure. The figure is one credit per deal, investor or parcel new to the account, plus the tool’s base, and nothing for a record already seen. Every result reports it in meta.credits.charged, beside used, line and period_end. Those three are the period’s count after the call, the figure it stops at, and when it resets, so a client can predict the stop. See Keys and headers. A spent allowance, or on Growth and Scale the ceiling, then arrives as a quota_exceeded tool result. The tool sends no page and charges nothing. Every charged tool answers so until the period resets or the plan changes, a re-read included. The handshake and the uncharged tools keep answering. The API writes a legacy client’s single-frame stream before the tool runs. So it charges such a client per call, refused or served, at the largest possible cost of its page. That cost is the tool’s base plus the asked page, clamped to 50 and the plan’s largest page. On a legacy client, the API refuses a call whose weight passes the line as a real 403 quota_exceeded. The refusal comes before the tool runs and before the API counts anything. It reads the envelope only for the tool’s name and the asked page, which set the weight. A call the tool then refuses, for example with a plan_limit or a validation error, still costs that weight. So on a legacy client, ask for the plan’s page or less. initialize, tools/list, resources/read and prompts/get cost nothing. The SDK’s own refusals never reach a tool and carry no problem body. A schema failure is an isError result with one line of text and no structuredContent, for example Input validation error: Invalid arguments for tool godmode_deal_get: data/deal_id must match pattern "^deal_[0-9a-f]{32}$". A typed id field carries its pattern in the schema, so the SDK catches a wrong prefix here, not the plan. That is why invalid_id is not in the table above. An unknown tool name is a JSON-RPC error instead, {"error": {"code": -32602, "message": "Tool godmode_nope not found"}}. Three more refusals occur at the transport: a batched array is 400, any Origin header is 403, and GET and DELETE are 405.

Partners and staff

On the internal host, rate limiting has two parts. Staff and contracted partners reach that host on the company network with a gm_ key. The API enforces the key budget (rpm, 120 by default) and the 240 per minute per (key, X-On-Behalf-Of) bucket before the SDK runs. So they answer 429 with the problem body, a Retry-After header and the x-ratelimit-* trio, and the API never reads the JSON-RPC message. The API charges the contact bucket, 30 per minute per (key, X-On-Behalf-Of), inside the tool wrapper each time the contact gate is open. So it arrives as an isError result with code: "rate_limited", retry_after and bucket on the body. The API spends the unit even when the record holds nothing. So a loop that opens one profile after another stops after 30 in a minute for that person. The header refusals of a contact-scope key, on_behalf_of_required and the prefixed-form validation_error, are on Keys and headers.