Skip to content

Reference

Errors

SurrealDB Agent Memory uses standard HTTP status codes, and every REST error response carries the same small JSON body.

All errors return a JSON body with a single field:

{
  "message": "context acme-staging does not exist"
}
FieldDescription
messageA human-readable explanation of this error.

The status is sent only as the HTTP status code of the response. The body has no status, type, title or instance field.

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

  • Malformed JSON body

  • Missing required fields

  • Invalid field values, such as a malformed scope path or pattern, label, entity reference, pagination cursor or model configuration

  • A write to a scope that is not registered

  • An empty file upload

{
  "message": "the uploaded file is empty"
}

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

{
  "message": "missing or invalid Authorization bearer token"
}

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

  • The requested scope falls outside the key's granted region for the verb

  • A write targets a scope outside the key's write region

  • A request acts on behalf of a principal that does not allow delegation

  • The operation targets a reserved principal or scope, such as updating the system principal or tombstoning the root scope /

{
  "message": "the root scope `/` cannot be tombstoned"
}

The requested resource does not exist.

{
  "message": "document with id 0197d8f2 does not exist"
}

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

  • A configured limit is exceeded (ScopeLimitExceeded), such as the scope depth or the number of labels on a fact

  • A connector or binding already exists, a connector is still in use, or a binding action is not valid from its current status

{
  "message": "context already exists"
}

The uploaded file exceeds the upload limit. The limit is set for the whole deployment, not for each Context, and is 4 GiB by default.

The uploaded file has a media type that the server cannot ingest.

{
  "message": "unsupported media type: application/x-msdownload"
}

The request is syntactically valid but semantically invalid. Examples are an invalid Context id, namespace or database name (including the reserved spectron namespace), a malformed structured fact entry, or an uncertainty flag that names no subject.

{
  "message": "invalid namespace: spectron"
}

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 (for example, organisation credit enforcement on SurrealDB Cloud). 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.

{
  "message": "token limit exceeded for context"
}

An unexpected error occurred server-side. The message does not include internal details, which are written to the server log instead.

{
  "message": "unknown error"
}

The request needs connectors, but connectors are disabled on this deployment or no connector host is configured.

A call to the connector host, or to the customer data source beyond it, failed. The message carries a reason code, never a source credential.

The server is temporarily unable to handle requests. This covers a SurrealDB connection issue or startup, and also load shedding: a serving pod above its in-flight request cap sheds new work rather than queueing it. Every end-user operation can answer 503; GET /health is exempt so a shedding pod still reports its own state.

A shed response carries a Retry-After header in delta-seconds alongside the usual error body. Every other 503 from the server carries the header as well.

HTTP/1.1 503 Service Unavailable
Retry-After: 4

Wait the larger of your own backoff and the advertised delay before retrying. Jittered backoff alone typically starts below Retry-After, so a client that ignores the header can exhaust its attempts inside the window the server asked it to sit out, and every one of those attempts lands on a pod that is still saturated.

Only the delta-seconds form is emitted. A client that encounters the HTTP-date form should fall back to plain backoff.

Note

Browser clients can read this header because the CORS policy names it in Access-Control-Expose-Headers. Retry-After is not one of the CORS-safelisted response headers, so a caller reading it from script in a different origin without that exposure would see null and quietly lose the delay.

ExceptionHTTP statusWhen
MemoryAuthError401Missing or invalid API key
MemoryScopeError403Scope floor or principal rejects the call
MemoryNotFoundError404Resource does not exist
MemoryAPIErrorOther non-2xxGeneric API failure (includes 400, 409, 429, 5xx)
from surrealdb.memory import MemoryNotFoundError, MemoryAPIError

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

See the SDK error sections for Python and JavaScript for the full hierarchy and retry behaviour.

Error classHTTP statusWhen
AuthError401Missing or invalid API key
ScopeError403Scope floor or principal rejection
NotFoundError404Resource does not exist
ValidationError400, 422Invalid request payload
RateLimitError429Token or rate limit exceeded
ServerError5xxServer-side failure
ConnectionError-Network failure or timeout
StreamError-A streamed response failed part of the way through
CancelledError-The caller aborted the request through its signal
import { NotFoundError, RateLimitError } from "@surrealdb/memory";

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

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

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

Poll GET /api/v1/{context_id}/documents/{id} to check document status. See Uploading documents for retry guidance.

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.

Was this page helpful?