> ## Documentation Index
> Fetch the complete documentation index at: https://developers.investorlift.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors over MCP

> A failure inside a tool is a tool result, not a transport error: the codes, the recovery sentences, ambiguity, rate limits and the SDK refusals.

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:

```json theme={null}
{ "isError": true,
  "content": [{ "type": "text", "text": "outside_coverage: the point 40.7128, -74.006 is outside every loaded market's point tolerance (loaded: hou within 2 mi of its bbox, phx within 20 mi of its bbox); see meta.coverage[].bbox and point_tolerance_miles on any list response. No data at this location; the loaded markets and their areas are listed in markets[] when present, and in meta.coverage[] on any list response." }],
  "structuredContent": { "error": { "type": ".../guides/concepts/errors#outside_coverage", "title": "Outside coverage", "status": 422, "code": "outside_coverage", "instance": "/mcp", "request_id": "ca4c1a3e-...",
    "detail": "the point 40.7128, -74.006 is outside every loaded market's ...", "recovery": "No data at this location; ...",
    "markets": [ { "market": "hou", "state": "TX", "bbox": [-95.95790786, 29.501148, -94.91426317, 30.166826], "point_tolerance_miles": 2, "universe_kind": "county" }, "... one entry per loaded market" ] } } }
```

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:

| Code                    | Status | When                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `recovery`                                                                                                                                                                                          |
| ----------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `validation_error`      | 400    | a parameter failed validation, an exactly-one group was empty or doubled, `limit` over the format's cap, the byte guard                                                                                                                                                                                                                                                                                                                                                                                                         | Fix the parameters named in `errors[]` and send the request again.                                                                                                                                  |
| `unknown_parameter`     | 400    | the encoded query carries a parameter the underlying route does not define                                                                                                                                                                                                                                                                                                                                                                                                                                                      | Remove the parameter, or write the list as comma-separated values or repeated keys, for example `kind=flip,hold`.                                                                                   |
| `invalid_cursor`        | 400    | the cursor is undecodable, or the API issued it for another query, sort, weight set or dataset version                                                                                                                                                                                                                                                                                                                                                                                                                          | Restart from page 1 without `cursor`; the data refreshed or the query changed.                                                                                                                      |
| `geometry_required`     | 400    | no geometry where the tool needs one                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | Pass one geometry: `lat` and `lng` with `radius_miles`, `bbox`, `property_id`, `zip` (a list) or `city`.                                                                                            |
| `geometry_conflict`     | 400    | `radius_miles` with `bbox`, or `property_id` beside either. A lone latitude or longitude. `zip` beside `city`, or a place beside `radius_miles`, `bbox` or `property_id`. `zip` / `city` on `godmode_buyers_match`                                                                                                                                                                                                                                                                                                              | Send one geometry only: lat and lng with `radius_miles`, bbox alone, `property_id` with `radius_miles`, or zip or city. On the coverage route, pass one place: lat and lng, county, zip or market.  |
| `sort_requires_point`   | 400    | `sort: "distance"` with a `bbox`, a `zip` or a `city` and no point                                                                                                                                                                                                                                                                                                                                                                                                                                                              | Add `lat` and `lng` or `property_id`, or drop `sort=distance` and take the default date order.                                                                                                      |
| `market_required`       | 400    | the id exists in more than one loaded market. Or you called `godmode_lenders_top`, which ranks inside one market, with no `market` on a host with several loaded markets. `markets[]` lists them                                                                                                                                                                                                                                                                                                                                | The id exists in each listed market, or the list ranks inside one; pass market.                                                                                                                     |
| `plan_limit`            | 403    | a `limit` over the plan's largest page. A `weeks` over 52 on Free and Starter on `godmode_market_changes` (`limit: "weeks"`, `cap: 52`). A search wider than the plan allows. The name-search day cap. The body names the limit                                                                                                                                                                                                                                                                                                 | Narrow the request to what the body names as the limit, or upgrade the plan in the developer console.                                                                                               |
| `quota_exceeded`        | 403    | on a client that negotiates 2026-07-28, you spent the period's credits: the allowance, or on Growth and Scale the ceiling. The tool sends no page and charges nothing. At the line, every charged tool answers it until the period resets or the plan changes. That includes a re-read of a record already held. The handshake and the uncharged tools keep answering. A legacy client meets it as a real `403` before the tool runs (below)                                                                                    | Upgrade the plan in the developer console, or wait for the billing period to reset.                                                                                                                 |
| `not_found`             | 404    | no such deal, investor, parcel, agent, lender, listing or company in any loaded market                                                                                                                                                                                                                                                                                                                                                                                                                                          | Check the path and the id; the entity is in no loaded market, or the route does not exist.                                                                                                          |
| `gone`                  | 410    | a retired investor, agent or lender id, with no successor                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | This id is retired and has no successor; look the investor, agent or lender up by name.                                                                                                             |
| `outside_coverage`      | 422    | the point or viewport misses every market's tolerance. `markets[]` carries the boxes. Also a `zip` or `city` that no parcel of a loaded market carries, on the deals, investors and wholesale tools. The body then carries `zips_unknown` or `city` beside `markets[]`. Also a ZIP, city or county on `godmode_lenders_top` outside the counties its market's lender registry covers (`meta.coverage[].lenders.counties`). The body then carries `counties_covered`. A ZIP of an unloaded county is not a place with no lending | No data at this location. Read the loaded markets and their areas from `markets[]` when present, and from `meta.coverage[]` on any list response.                                                   |
| `addresses_unavailable` | 422    | `address` on `godmode_property_lookup` while no market has a published address table. `address_as_of` is then null on every market in `godmode_coverage`                                                                                                                                                                                                                                                                                                                                                                        | Resolve by lat / lng or apn, or retry once `coverage[].address_as_of` carries a date.                                                                                                               |
| `listings_unavailable`  | 422    | `listing_status` on the investor deals tool, or `source: mls` on the comps tool, for a market with no listing tables                                                                                                                                                                                                                                                                                                                                                                                                            | Not published for this market yet. The API still serves deal, investor and property rows with the listing block null, and the comps from the deed registry alone.                                   |
| `agents_unavailable`    | 422    | any agent path for a market with no agent registry                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | Not published for this market yet. The API still serves the listing agents on deal and property rows, without ids.                                                                                  |
| `lenders_unavailable`   | 422    | a lender tool for a market with no lender registry (`meta.coverage[].lenders` null there). The borrowers tool on a registry built before its borrower match. The nearby tool, `period` on the top tool, or the loans tool's `foreclosed`, `cell` or geometry on a registry built before the place rankings. The same on a host without the h3 extensions                                                                                                                                                                        | Not published for this market yet, or its registry predates the borrower match or the place rankings the request asked for. The API still serves the financing block on parcels where the slice is. |
| `wholesale_unavailable` | 422    | a wholesale tool or the Investorlift filters for a market with no wholesale tables                                                                                                                                                                                                                                                                                                                                                                                                                                              | Not published for this market yet; deal, investor and property rows are still served with their wholesale blocks null.                                                                              |
| `cash_sale_unavailable` | 422    | the `cash-buyer` quicklist or `filters.sale.cash_sale` on `godmode_property_search` for a market whose coverage row carries `sale_mortgage_measured: false`. In that market the delivery records a purchase mortgage on fewer than one priced last sale in five, so `cash_sale_proxy` is null on every parcel                                                                                                                                                                                                                   | Not measured for this market: drop cash-buyer and `sale.cash_sale`. Where `auction_counted` is true, `bought_auction_kind`, `buys_at_auction` and `buys_reo` are the measured cash signals.         |
| `rate_limited`          | 429    | you spent the plan's requests a minute or a day. The gateway answers before it reads the message (see below)                                                                                                                                                                                                                                                                                                                                                                                                                    | Retry after `retry_after` seconds.                                                                                                                                                                  |
| `internal_error`        | 500    | anything unexpected. The API echoes nothing from the database                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | Retry once. If it occurs again, report the `request_id`.                                                                                                                                            |
| `database_unavailable`  | 503    | the API cannot reach the database                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Retry with backoff. The database was unreachable and the API did not run the request.                                                                                                               |
| `pool_saturated`        | 503    | no pooled connection in 2 s, or the per-pod in-flight cap                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Retry in a second. The service is at capacity and did not run the request.                                                                                                                          |
| `ledger_unavailable`    | 503    | the API cannot reach the credit ledger. `retry_after` is on the body                                                                                                                                                                                                                                                                                                                                                                                                                                                            | Retry after `retry_after` seconds. The API charged nothing and served nothing.                                                                                                                      |
| `statement_timeout`     | 504    | the query passed the 10 s statement timeout                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | Narrow the radius or add filters. The query exceeded 10 seconds.                                                                                                                                    |

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:

```json theme={null}
{ "code": "validation_error", "detail": "One of investor_id, name is required.", "errors": [ { "param": "investor_id", "message": "exactly one of investor_id, name", "code": "missing" }, { "param": "name", "message": "exactly one of investor_id, name", "code": "missing" } ] }
{ "code": "unknown_parameter", "detail": "Unknown parameter: kind[]. List values are comma-separated (kind=flip,hold) or repeated keys (kind=flip&kind=hold), never bracketed." }
{ "code": "market_required", "detail": "wsr_eea85093d3ae exists in 2 loaded markets (hou, phx); pass market=.", "errors": [ { "param": "market", "message": "one of hou, phx", "code": "market_required" } ], "markets": ["hou", "phx"] }
```

**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](/guides/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](/mcp/keys).

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](/mcp/keys).


## Related topics

- [Plans and limits](/guides/plans-and-limits.md)
- [Changelog](/changelog.md)
- [Keys and headers](/mcp/keys.md)
- [Errors](/guides/concepts/errors.md)
- [The MCP endpoint](/mcp/overview.md)
