Error codes reference
Every query and HTTP error in Relata carries a structured RFC 7807
application/problem+json response. This page enumerates the error
variants, their causes, and the suggested fix.
Deep-linkable errors. Every error response carries a
typeURI of the formhttps://relatadb.dev/errors/<code>, and each code resolves to a dedicated page with the cause and remediation. Look up any code (in any form the server emits) at relatadb.dev/errors — or click the code column below.
RFC 7807 response shape
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"type": "https://relatadb.dev/errors/parse",
"title": "Bad Request",
"status": 400,
"detail": "unexpected token 'SELEC' after query end",
"instance": "/query",
"correlation_id": "0192a4f8-7e1c-7c3a-9b02-2c4d5e6f7a8b"
}| Field | Meaning |
|---|---|
type | URI identifying the error class — stable across releases |
title | HTTP status phrase (per RFC 9110) |
status | HTTP status code |
detail | Human-readable cause (English; may change between releases) |
instance | The route that produced the error |
correlation_id | UUIDv7 — cite this in ops tickets |
HTTP status semantics
| Status | Meaning | Retry? |
|---|---|---|
| 400 | Bad Request — client-side problem (parse error, bad argument) | No — fix the request |
| 401 | Unauthorized — missing or invalid bearer token | No — supply a token |
| 403 | Forbidden — ACL denied access (the principal lacks a permission) | No — request access |
| 404 | Not Found — the type, route, or backup artefact does not exist | No |
| 405 | Method Not Allowed — wrong HTTP verb on a known route | No |
| 409 | Conflict — write lease held by another node; or row already exists | Yes, after lease TTL |
| 413 | Payload Too Large — exceeded DefaultBodyLimit | No — chunk the request |
| 422 | Unprocessable Entity — semantically invalid (e.g. backup SHA mismatch) | No |
| 429 | Too Many Requests — admission control or rate limit hit | Yes, after Retry-After — see Rate-limit headers |
| 500 | Internal Server Error — server-side bug or storage failure | Maybe, with backoff |
| 501 | Not Implemented — recognised but unimplemented feature | No |
| 503 | Service Unavailable — server is draining or shutting down | Yes, after restart |
QueryError variants (canonical list)
These are the variants of the query error type that surface as RFC 7807 errors.
| Variant | code | HTTP | Cause | Suggested fix |
|---|---|---|---|---|
MissingPurpose | REL_PURPOSE | 403 | Query lacks PURPOSE (only raised in strict mode — RELATA_PURPOSE_MODE=strict) | Prefix the query with PURPOSE '<id>' or relax the mode |
Timeout | REL_TIMEOUT | 408 / 504 | Execution exceeded RELATA_QUERY_TIMEOUT_SECS | Add LIMIT, add an index, or raise the timeout |
WatchLimit | REL_WATCH_LIMIT | 429 | RELATA_MAX_WATCH_SUBSCRIPTIONS reached | Close unused subscriptions or raise the cap |
UnknownPurpose | REL_PURPOSE_UNKNOWN | 403 | Purpose not registered in PurposeRegistry | Register it via the config, or use a known purpose |
ParseError | REL_PARSE | 400 | SQL / Cypher / GQL syntax error | Fix the syntax (v1.5.0 adds line:column) |
UnknownType | REL_UNKNOWN_TYPE | 404 | Object type not registered | POST /types to register it |
UnknownColumn | REL_UNKNOWN_COLUMN | 400 | Column not in the type's declared contract | Check the type definition or remove the column |
AccessDenied | REL_ACL | 403 | ACL denied the principal access | Grant the permission via the policy table |
Storage | REL_STORAGE | 500 | Storage error (disk full, manifest corruption, etc.) | Check /debug/stats; contact ops |
HumintProtectedType | REL_HUMINT | 403 | Tried to read a HUMINT-protected type without break-glass | Use the break-glass unmask flow |
MissingAccessScope | REL_ACCESS_SCOPE | 403 | Type requires access_scope_ref | Pass the scope reference |
CrossOrganizationAccessDenied | REL_XORG | 403 | No SharingAgreement covers this cross-org access | Establish a sharing agreement |
QuotaExceeded | REL_QUOTA | 429 | Tenant admission quota exceeded | Wait, or raise the tenant quota |
ResultCapExceeded | REL_RESULT_CAP | 406 | Result exceeded RELATA_MAX_RESULT_ROWS | Add LIMIT, or raise the cap (carefully) |
VectorBudgetExceeded | REL_VECTOR_BUDGET | 406 | Vector k exceeds RELATA_MAX_VECTOR_K | Lower the LIMIT, or raise/disable the budget via RELATA_MAX_VECTOR_K |
MaskedColumnInAggregate | REL_MASKED_COLUMN | 400 | ACL-masked column used as aggregate/sort/group target | Remove the masked column from aggregates |
QueueFull | REL_QUEUE_FULL | 429 | All query priority queues are at capacity | Retry with backoff |
ProtectedTypeColumn | REL_PROTECTED_COLUMN | 403 | Column name references a protected type | Remove the protected column from the query |
InternalError | REL_INTERNAL | 500 | Bug; the detail is masked in client_safe_msg | File an issue with the correlation_id |
Rate-limit headers
All 429 responses from the per-IP token-bucket rate limiter include the following headers:
| Header | Value | Description |
|---|---|---|
Retry-After | seconds (integer) | Minimum seconds before retrying — always 1 for the normal rate-limit bucket |
X-RateLimit-Limit | integer | Requests-per-second quota that applies to this endpoint |
X-RateLimit-Remaining | integer | Tokens remaining in the current window — always 0 on a 429 |
X-RateLimit-Reset | Unix epoch seconds | When the next token becomes available |
Example 429 response:
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 1
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1753128001
{
"type": "https://relatadb.dev/errors/REL_RATE_LIMITED",
"title": "Too Many Requests",
"status": 429,
"detail": "Per-IP rate limit exceeded for /query.",
"instance": "/query",
"retryable": true
}SDK clients should inspect Retry-After and back off before retrying.
Quota values are controlled by RELATA_RATE_LIMIT_RPS, RELATA_READ_RATE_LIMIT_RPS,
and RELATA_MEMORY_RATE_LIMIT_RPS (see Environment Variables).
SDK error mapping
Each SDK maps the RFC 7807 response to a typed exception:
Python
from relata.exceptions import (
RelataParseError, RelataAccessDenied, RelataQuotaExceeded,
RelataTimeout, RelataResultCapExceeded, RelataError,
)
try:
client.query("SELEC * FROM Person")
except RelataParseError as e:
print(e.detail) # "unexpected token 'SELEC' after query end"
print(e.correlation_id) # "0192a4f8-..."
except RelataAccessDenied as e:
print(f"missing permission: {e.detail}")
except RelataError as e: # catch-all parent
print(e.type_uri, e.status)TypeScript
import {
RelataParseError, RelataAccessDenied, RelataQuotaExceeded,
RelataError,
} from "@zysec-ai/relata-sdk";
try {
await relata.query({ sql: "SELEC * FROM Person" });
} catch (e) {
if (e instanceof RelataParseError) {
console.error(e.detail); // string
console.error(e.correlationId); // UUIDv7
} else if (e instanceof RelataError) {
console.error(e.typeUri, e.status);
}
}Go
import "github.com/relatadb/RelataDB/sdks/go/relata"
var errRelata *relata.Error
if errors.As(err, &errRelata) {
log.Printf(
"type=%s status=%d detail=%s correlation=%s",
errRelata.Type, errRelata.Status, errRelata.Detail, errRelata.CorrelationID,
)
}Common error scenarios
"no PURPOSE declared"
You're in strict mode (RELATA_PURPOSE_MODE=strict). Either prefix your query
with PURPOSE '<id>' or set RELATA_PURPOSE_MODE=open for dev.
"access denied: principal 'X' cannot perform 'read' on 'Person'"
The principal's ACL role lacks read on Person. Add a policy:
PURPOSE 'admin' INSERT INTO Policy (principal, permission, object_type)
VALUES ('alice@example.com', 'read', 'Person')"WATCH rejected: subscription limit reached"
Each WATCH subscription re-evaluates on every commit, so the cap
(RELATA_MAX_WATCH_SUBSCRIPTIONS) protects commit latency. Close unused
subscriptions, or raise the cap on a write-light workload.
"ResultCapExceeded"
Your query result is larger than RELATA_MAX_RESULT_ROWS (default 1,000,000).
Add LIMIT N, narrow the WHERE, or — for legitimate bulk exports — use
GET /export instead of /query.
"VectorBudgetExceeded"
Vector search clamps the requested k to RELATA_MAX_VECTOR_K (default 1000).
Lower the requested LIMIT, or raise/disable the cap via RELATA_MAX_VECTOR_K=0.
Reporting errors
When filing an issue, always include:
- The
correlation_id(UUIDv7) from the response. - The
typeURI. - The full
detailstring. - The server version (
/version). - The route (
instancefield). - The query or request body that triggered it.
Without the correlation_id, ops cannot trace the request through the logs.