# Error Model ## Stable categories - `AUTHENTICATION_REQUIRED` - `TOKEN_INVALID` - `ACCESS_DENIED` - `PROJECT_NOT_FOUND` - `RESOURCE_NOT_REGISTERED` - `CONNECTOR_DISABLED` - `CONNECTOR_UNAVAILABLE` - `TARGET_UNAVAILABLE` - `VALIDATION_FAILED` - `RATE_LIMITED` - `TIMEOUT` - `RESULT_TOO_LARGE` - `UPSTREAM_PROTOCOL_ERROR` - `INTERNAL_ERROR` ## Response rules - do not reveal whether a forbidden resource exists when that leaks scope; - include a trace ID; - use retryability metadata; - keep user-facing messages useful but sanitized; - log detailed internal exceptions only in protected logs; - translate target-specific errors into stable categories while preserving a safe diagnostic code. - separate `retryable` from `safeToRetry`: transient writes may be retryable only after their exact state is reconciled, never by blind transport replay; - identify the failed operation and subsystem without returning target URLs, credentials, raw headers or stack traces; - include expected/actual state and a bounded recovery hint when optimistic state changed; - keep the stateless MCP session usable after an operation error. Example: ```json { "error": { "code": "CONNECTOR_UNAVAILABLE", "category": "transient", "message": "The Unraid inventory connector is temporarily unavailable.", "operation": "unraid.container.health", "subsystem": "upstream_unraid", "retryable": true, "safeToRetry": true, "correlationId": "...", "sessionId": null, "upstreamStatus": 503, "expectedState": null, "actualState": null, "recoveryHint": "Retry the unchanged read after the bounded backoff." } } ``` The dataplane retries only MCP protocol reads and catalogued R1 tools, once. Connector-local Gitea GETs use at most three bounded attempts. R2, R3 and unknown operations are never automatically retried.