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

# Error Handling

> Structured error responses with agent-friendly recovery hints

# Error Handling

All NexSpace API errors return a consistent JSON structure with machine-readable
codes and agent-friendly recovery hints.

## Error Format

```json theme={null}
{
  "error": {
    "message": "Human-readable description",
    "code": "MACHINE_CODE",
    "suggestion": "How to fix this — designed for LLM agents to self-correct",
    "retryable": false,
    "details": {}
  },
  "requestId": "req_abc123"
}
```

| Field        | Type     | Description                                      |
| ------------ | -------- | ------------------------------------------------ |
| `message`    | string   | Human-readable error message                     |
| `code`       | string   | Machine-readable error code                      |
| `suggestion` | string?  | Recovery hint for agents                         |
| `retryable`  | boolean? | Whether retry with backoff might succeed         |
| `details`    | object?  | Structured details (e.g., Zod validation issues) |
| `requestId`  | string?  | Correlation ID for support                       |

## Common Error Codes

| HTTP | Code                                     | Meaning                                          | `retryable` |
| ---- | ---------------------------------------- | ------------------------------------------------ | ----------- |
| 400  | `VALIDATION_FAILED` / `VALIDATION_ERROR` | Request body failed schema validation            | `false`     |
| 401  | `UNAUTHENTICATED`                        | Missing authentication                           | `false`     |
| 401  | `API_KEY_REQUIRED`                       | Endpoint requires an API key (not a session/JWT) | `false`     |
| 401  | `INVALID_API_KEY`                        | API key not found, revoked, or expired           | `false`     |
| 401  | `USER_INACTIVE`                          | The account behind the credential is deactivated | `false`     |
| 403  | `INSUFFICIENT_SCOPE`                     | API key/token lacks a required scope             | `false`     |
| 403  | `IP_NOT_ALLOWED`                         | Request IP is not in the key's allowlist         | `false`     |
| 403  | `FORBIDDEN`                              | Insufficient permissions                         | `false`     |
| 404  | `NOT_FOUND`                              | Resource does not exist                          | `false`     |
| 409  | `IDEMPOTENCY_CONFLICT`                   | Idempotency key reused with a different body     | `false`     |
| 409  | `ALREADY_REVOKED`                        | API key already revoked                          | `false`     |
| 410  | `VERSION_SUNSET`                         | API version has been removed                     | `false`     |
| 429  | `API_KEY_RATE_LIMITED`                   | Per-key rate limit exceeded                      | `true`      |
| 500  | `INTERNAL_ERROR`                         | Server error                                     | `true`      |

When `retryable` is omitted, infer it from the status: `429`, `503`, `408`,
`502`, and `504` are retryable; other `4xx` are not.

## Errors over MCP

Tool calls to the [MCP server](/concepts/mcp) return errors two ways:

* **Protocol errors** (unknown tool, bad params, insufficient scope) come back as
  a JSON-RPC `error` object. The `error.data` carries `suggestion` and
  `retryable`, mirroring the REST contract.
* **Execution failures** (the tool ran but failed) come back as a normal
  `tools/call` result with `isError: true`. The `content[].text` payload includes
  the failure message followed by a `Suggestion:` line so the calling model can
  self-correct.

## Best Practices

1. **Check `retryable`** — if `true`, retry with exponential backoff; if `false`, reformulate the request instead of retrying
2. **Read `suggestion`** — it tells agents exactly how to fix the issue
3. **Log `requestId`** — include it when contacting support
4. **Handle `code` programmatically** — don't parse `message` strings
