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

# MCP tool errors

> The ToolError contract returned by Cotality MCP tools — error codes, the retryable flag, and the retry strategy an agent should apply to each code.

Cotality MCP tools signal failure with a structured `ToolError` rather than a `success: false` envelope. The CLIP, property characteristics, risk, and market analytics tools all use the same shape, so an agent can handle them with one code path.

<Note>
  **Retryable** means the request could succeed if you send it again unchanged — a transient
  upstream fault, not a problem with your input. A non-retryable error will fail identically
  every time until you change something.
</Note>

<Note>
  This page covers the **MCP tool** error contract. For the REST APIs and their HTTP status
  codes, see [Error handling](/reference/error-handling).
</Note>

## Error shape

A failed `tools/call` returns an error whose content is a JSON object with three fields:

```json theme={null}
{
  "code": "UPSTREAM_TIMEOUT",
  "message": "The upstream data service timed out. Retry shortly.",
  "retryable": true
}
```

| Field       | Type      | Description                                                                |
| ----------- | --------- | -------------------------------------------------------------------------- |
| `code`      | `string`  | Stable, machine-readable error class. Branch on this, never on `message`.  |
| `message`   | `string`  | Human-readable explanation. Safe to surface to a user; wording may change. |
| `retryable` | `boolean` | Whether re-issuing the identical request could succeed.                    |

## Error codes

| Code               | `retryable` | Cause                                                                                                 | What to do                                                 |
| ------------------ | ----------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `INVALID_INPUT`    | `false`     | Input failed validation — wrong type, out-of-range value, too many items, or a malformed identifier   | Correct the arguments. The same request will always fail.  |
| `NOT_FOUND`        | `false`     | **CLIP tools only.** The upstream lookup service explicitly reported no match for the CLIP or address | Report no match to the user. Never fabricate a value.      |
| `UPSTREAM_ERROR`   | `true`      | A downstream Cotality data service returned an error                                                  | Retry with exponential backoff, capped at 3 attempts.      |
| `UPSTREAM_TIMEOUT` | `true`      | A downstream Cotality data service did not respond in time                                            | Retry with exponential backoff, capped at 3 attempts.      |
| `INTERNAL_ERROR`   | `false`     | An unexpected server-side failure                                                                     | Do not retry. Escalate to Cotality support if it persists. |

<Warning>
  `NOT_FOUND` is raised only by `clip-find_property_by_clip` and `clip-find_property_by_address`.
  The property characteristics, risk, and analytics tools have no "not found" error at all.
  On those tools an unmatched CLIP or an uncovered geography comes back as a **successful**
  response with `count` of `0` and an empty `data` array. An agent that waits for an error will
  silently treat "no data" as "still loading". Always check `count`.
</Warning>

## Using the `retryable` flag

`retryable` is the single most actionable field for an agent. It removes the need to maintain a
list of which codes are transient.

```text theme={null}
result = call_tool(name, arguments)

if result.is_error:
    err = parse(result)
    if err.retryable and attempt < 3:
        wait = min(base * (2 ** attempt) + jitter, max_wait)
        sleep(wait); retry()
    else:
        surface_to_user(err.message); stop()
```

<Warning>
  Never retry a `retryable: false` error. `INVALID_INPUT` and `NOT_FOUND` are deterministic —
  a retry loop burns your [rate limit](/reference/rate-limits) without any chance of success.
</Warning>

## Input limits

Most `INVALID_INPUT` errors come from these constraints. Validate against them before calling.

### CLIP inputs

Applies to `pd-get_property_characteristics`, `at-get_property_analytics`, `at-get_property_roof_age`, and `at-get_property_climate_risk`.

| Constraint            | Value                                            |
| --------------------- | ------------------------------------------------ |
| `clips` minimum items | 1                                                |
| `clips` maximum items | 50 per request                                   |
| Item format           | Non-empty numeric string — digits only (`^\d+$`) |

For `clip-find_property_by_clip`, the single `clip` argument follows the same digit-only rule.

<Tip>
  Batch up to 50 CLIPs in one call rather than issuing 50 single-CLIP calls. It is one
  request against your quota instead of 50.
</Tip>

### Analytics filter inputs

Applies to all five market and trend analytics tools.

| Constraint                          | Value                                                                                         |
| ----------------------------------- | --------------------------------------------------------------------------------------------- |
| `geographyTypeValues` minimum items | 1                                                                                             |
| `geographyTypeValues` maximum items | 50 per request                                                                                |
| Item format                         | Non-empty alphanumeric string (`^[a-zA-Z0-9]+$`) — no spaces, hyphens, or punctuation         |
| `year`                              | Integer, 1900–2100                                                                            |
| `month`                             | Integer, 1–12                                                                                 |
| `timeRange`                         | Omit entirely for the latest period. If present, both `startDate` and `endDate` are required. |

<Warning>
  A partial `timeRange` — one of `startDate` or `endDate` — is rejected with `INVALID_INPUT`.
  Omit the whole object to get the latest available data.
</Warning>

## Scope failures

A tool called without its required scope fails before the tool executes. Treat it as permanently non-retryable and contact your account team to request the missing entitlement.

## Related

* [Rate limits](/reference/rate-limits) — quota and `429` back-off
* [Error handling](/reference/error-handling) — REST API status codes
