# Errors

Error codes and troubleshooting.

SurrealDB Agent Memory uses standard HTTP status codes and follows [RFC 7807 Problem Details](https://www.rfc-editor.org/rfc/rfc7807) for all error responses.

> [!NOTE]
> `Spectron` was the project name for SurrealDB Agent Memory. These type names
> will be renamed in a future release.

## Error response format

All errors return a JSON body with the following fields:

```json
{
  "type": "https://spectron.dev/errors/context-not-found",
  "title": "Context not found",
  "status": 404,
  "detail": "No context with id 'acme-staging' exists on this server.",
  "instance": "/api/v1/contexts/acme-staging"
}
```

| Field | Description |
|---|---|
| `type` | A URI identifying the error type. Stable across versions. |
| `title` | A short, human-readable summary of the problem type. |
| `status` | The HTTP status code. |
| `detail` | A human-readable explanation specific to this occurrence. |
| `instance` | The request path that produced the error. |

## HTTP status codes

### 400 Bad Request

The request body or query parameters are invalid. Common causes:

- Malformed JSON body
- Missing required fields
- Invalid field values (e.g. unknown `mode` for knowledge query)
- Scope floor violation (requesting a scope narrower than the key's floor)

```json
{
  "type": "https://spectron.dev/errors/validation-error",
  "title": "Validation error",
  "status": 400,
  "detail": "Field 'mode' must be one of: vector, bm25, hybrid, hybrid_graph. Got: 'fuzzy'.",
  "instance": "/api/v1/acme-prod/query"
}
```

### 401 Unauthorized

No `Authorization: Bearer` token was provided, or the key is malformed.

```json
{
  "type": "https://spectron.dev/errors/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "API key missing. Include 'Authorization: Bearer <key>' in your request.",
  "instance": "/api/v1/acme-prod/sessions"
}
```

### 403 Forbidden

The API key is valid but does not have permission to perform this operation. Common causes:

- Agent key attempting a management operation
- The requested scope falls outside the key's granted region for the verb
- Agent key attempting to persist a reflection (requires supervisor principal)

```json
{
  "type": "https://spectron.dev/errors/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "This operation requires a management key. The provided key has principal 'agent'.",
  "instance": "/api/v1/contexts"
}
```

### 404 Not Found

The requested resource does not exist.

```json
{
  "type": "https://spectron.dev/errors/not-found",
  "title": "Not found",
  "status": 404,
  "detail": "No document with id 'document:0197d8f2...' exists in context 'acme-prod'.",
  "instance": "/api/v1/acme-prod/documents/0197d8f2"
}
```

### 409 Conflict

A resource with the same identifier already exists, or an idempotency conflict occurred:

- Duplicate Context id on create
- Duplicate **principal `display_name`** on create (when no matching `external_id` create-or-get applies)
- Same `Idempotency-Key` with a **different** request body on `/facts` or `/facts/batch`
- Duplicate idempotency request while the first is still **in flight**

```json
{
  "type": "https://spectron.dev/errors/conflict",
  "title": "Conflict",
  "status": 409,
  "detail": "A context with id 'acme-prod' already exists.",
  "instance": "/api/v1/contexts/acme-prod"
}
```

### 413 Payload Too Large

The uploaded file exceeds the per-Context size limit.

```json
{
  "type": "https://spectron.dev/errors/payload-too-large",
  "title": "Payload too large",
  "status": 413,
  "detail": "Uploaded file size (52.4 MB) exceeds the per-context limit (50 MB).",
  "instance": "/api/v1/acme-prod/documents"
}
```

### 422 Unprocessable Entity

The request is syntactically valid but semantically invalid.

```json
{
  "type": "https://spectron.dev/errors/unprocessable",
  "title": "Unprocessable entity",
  "status": 422,
  "detail": "Cannot bind context to namespace 'spectron' - this namespace is reserved for internal use.",
  "instance": "/api/v1/contexts/test"
}
```

### 429 Too Many Requests

The Context is blocked from LLM-backed work, or a per-minute rate limit was hit.

**Token budget:** returns **`429`** when **`enforcement_blocked`** is `true` on the Context (org credit enforcement on Cloud, or an operator-set block). A soft **`token_limit` breach alone does not reject** while `enforcement_blocked` is `false` - usage continues under pay-as-you-go.

Applies to LLM-backed paths including **`/chat`**, **`/facts?infer=full`**, **`/reflect`**, and **`/consolidate`** - not to read-only cache hits or direct lookups.

```json
{
  "type": "https://spectron.dev/errors/rate-limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Token enforcement is blocked for context 'acme-prod'.",
  "instance": "/api/v1/acme-prod/sessions/sess_abc/turns",
  "retry_after": null
}
```

### 500 Internal Server Error

An unexpected error occurred server-side. The `detail` field contains a request ID for support escalation.

```json
{
  "type": "https://spectron.dev/errors/internal",
  "title": "Internal server error",
  "status": 500,
  "detail": "An unexpected error occurred. Request ID: req_01HF3X...",
  "instance": "/api/v1/acme-prod/context"
}
```

### 503 Service Unavailable

The server is temporarily unable to handle requests - typically during a SurrealDB connection issue or startup.

## SDK exceptions

### Python SDK

| Exception | HTTP status | When |
|---|---|---|
| `SpectronAuthError` | 401 | Missing or invalid API key |
| `SpectronScopeError` | 403 | Scope floor or principal rejects the call |
| `SpectronNotFoundError` | 404 | Resource does not exist |
| `SpectronAPIError` | Other non-2xx | Generic API failure (includes 400, 409, 429, 5xx) |

```python
from surrealdb import SpectronNotFoundError, SpectronAPIError

try:
    doc = await client.documents.get("document:nonexistent")
except SpectronNotFoundError as e:
    print(f"Document not found: {e.message}")
except SpectronAPIError as e:
    if e.status_code == 429:
        print(f"Rate limit exceeded: {e.body}")
```

See the SDK error sections for [Python](/docs/agent-memory/integrations/sdks/python.md#errors-and-retries) and [JavaScript](/docs/agent-memory/integrations/sdks/javascript-and-typescript.md#errors-and-retries) for the full hierarchy and retry behaviour.

### JavaScript SDK

| Error class | HTTP status | When |
|---|---|---|
| `AuthError` | 401 | Missing or invalid API key |
| `ScopeError` | 403 | Scope floor or principal rejection |
| `NotFoundError` | 404 | Resource does not exist |
| `ValidationError` | 400, 422 | Invalid request payload |
| `RateLimitError` | 429 | Token or rate limit exceeded |
| `ServerError` | 500, 503 | Server-side failure |
| `ConnectionError` | - | Network failure or timeout |

```javascript
import { NotFoundError, RateLimitError } from "@surrealdb/spectron";

try {
    const doc = await client.documents.get("document:nonexistent");
} catch (e) {
    if (e instanceof NotFoundError) {
        console.error("Document not found:", e.detail);
    } else if (e instanceof RateLimitError) {
        console.error("Rate limit exceeded");
    }
}
```

## Ingestion pipeline errors

Documents that fail during async processing do not return HTTP errors - they set the document `status` to `"failed"` and populate the `error` field:

```json
{
  "id": "document:0197d8f2...",
  "status": "failed",
  "error": "PDF extraction failed: file is encrypted and no password was provided.",
  "processing_started_at": "2026-05-12T14:22:11Z",
  "processing_completed_at": "2026-05-12T14:22:14Z"
}
```

Poll `GET /api/v1/{context_id}/documents/{id}` to check document status. See [Uploading documents](/docs/agent-memory/ingest/authoritative/uploading-documents.md) for retry guidance.

## MCP error handling

MCP tool failures use a different envelope from REST, but carry the **same HTTP status semantics**:

- **Operation failures** → `isError: true` tool result with `structuredContent.error.status` (404, 403, 429, etc.). The JSON-RPC transport returns HTTP 200.
- **Protocol faults** → JSON-RPC `error` (bad params, unknown tool).

Auth failures and missing Contexts are masked as **401**, never 404, so unauthenticated callers cannot enumerate Context ids. See [MCP tools](/docs/agent-memory/reference/mcp-tools.md#error-handling).
