Skip to main content
View as Markdown

REST API Overview

Sovrium exposes a REST API over tables, records, views, activity, analytics and authentication. Every endpoint accepts and returns JSON, enforces RBAC with field-level permissions, and emits a single canonical error envelope.

The full path list lives in the Endpoint Reference; this page is the contract that governs all of it.

Base URL

All endpoints are relative to your Sovrium instance base URL.

code
http://localhost:3000/api

Authentication & access control

Authentication is session-based (Better Auth). Requests touching protected resources must carry one of exactly two credential forms:

Credential How it travels When it applies
Session cookie better-auth.session_token Always, once auth is configured. The browser default.
x-api-key: <key> An x-api-key request header When auth.apiKeys is on. See API Keys.

Access is governed by roles plus field-level permissions. Table permissions accept all, authenticated, or an arbitrary list of role names — so admin, member and viewer are the built-in defaults, not a fixed set. An app with no auth block evaluates requests under a guest role.

Per the anti-enumeration policy, an authenticated request that lacks access to a resource returns 404, never 403, so the caller cannot distinguish "not found" from "no access".

Error response contract

All 4xx/5xx responses use one canonical JSON envelope, so a single decoder covers every error.

app.json
{
  "success": false,
  "message": "Authentication required",
  "code": "UNAUTHORIZED"
}

code is drawn from a stable enum of thirteen values: UNAUTHORIZED, FORBIDDEN, NOT_FOUND, VALIDATION_ERROR, BAD_REQUEST, CONFLICT, PAYLOAD_TOO_LARGE, RATE_LIMITED, INTERNAL_ERROR, SERVICE_UNAVAILABLE, STORAGE_ERROR, DATABASE_ERROR, QUOTA_EXCEEDED. An optional error (legacy type identifier) and details (an array of strings) may also be present.

When the failure is field-level, an errors[] array names each offending field:

app.json
{
  "success": false,
  "message": "One or more fields failed validation",
  "code": "VALIDATION_ERROR",
  "errors": [{ "field": "id", "message": "Cannot write to readonly field 'id'" }]
}

A record create refused by the database populates the same array. When a CHECK, foreign-key, or NOT NULL constraint rejects a value on POST /api/tables/:table/records, the response names the column the constraint was declared on:

app.json
{
  "success": false,
  "message": "A submitted value is not allowed by this resource",
  "code": "VALIDATION_ERROR",
  "field": "status",
  "errors": [{ "field": "status", "message": "A submitted value is not allowed by this resource" }]
}

Only a field you actually sent is ever named, and the response never lists the values a field accepts. The column is recovered from the constraint that fired and is reported only when it matches a key in your payload, so an error response can never be used to discover a schema you cannot otherwise read. When no submitted key matches, field and errors[] are omitted and the class-level message answers on its own.

Attribution is limited to that single-record create path. A PATCH, or a batch write refused by the same constraint, answers the identical status and code with the class-level message and no field.

Status code Meaning
400 VALIDATION_ERROR Field-level validation failure (carries errors[])
400 BAD_REQUEST Malformed request
401 UNAUTHORIZED No / expired session
403 FORBIDDEN Authenticated, action denied (rare — see above)
404 NOT_FOUND Not found, or anti-enumeration access denial
409 CONFLICT Unique collision (single and batch) or stale optimistic write
413 PAYLOAD_TOO_LARGE Batch payload exceeds the hard limit
429 RATE_LIMITED Rate limit exceeded
500 INTERNAL_ERROR Unexpected server error (details redacted)

Batch size failures split across two codes: exceeding the per-operation maximum (1000 create, 100 update, 100 delete ids, 100 upsert) is a 400 VALIDATION_ERROR, while a payload over the hard 1000-id guard is 413.

How a request is framed never changes its code. A unique collision answers 409 whether it arrives as a single write or inside a batch — one handler covers both. The other constraint classes (check, foreign key, NOT NULL) answer 400 VALIDATION_ERROR on every path, since those reject the value that was sent rather than colliding with a row that already exists.

Configuration is code-only

There is no runtime schema-editing API. Sovrium is a configuration-as-code interpreter: you change an app by editing its app.ts / app.yaml and re-deploying — the same file the CLI validates offline with sovrium validate. The admin dashboard reflects runtime data, never configuration (Admin Dashboard); schema changes apply on the next boot (Migrations).

Health

Moved to Endpoint Reference.

Tables

Moved to Endpoint Reference.

Records

Moved to Endpoint Reference.

Views

Moved to Endpoint Reference.

Activity

Moved to Endpoint Reference.

Analytics

Moved to Endpoint Reference.

Authentication endpoints

Moved to Endpoint Reference.

Cross-cutting features

  • Pagination — list endpoints page their results (limit + offset).
  • Soft deletesDELETE trashes by default; ?permanent=true and ?purge=true hard-delete on the same route.
  • RBAC — role-gated on every protected route, with guest for auth-less apps.
  • Field-level permissions — granular read/write control per field per role.
  • Rate limiting — auth and admin endpoints are rate-limited.
  • Canonical error envelope — every 4xx/5xx shares the shape above.

Last updated August 28, 2026

This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.

Built with Sovrium