REST API

Every collection in your Radiant schema automatically gets a full set of REST endpoints. These endpoints handle CRUD operations, filtering, sorting, pagination, and authentication — no code required.

Base URL

All API endpoints are mounted under the API prefix defined in your config.radiant:

config {
  core: {
    api: {
      prefix: "/api"
    }
  }
}

If your prefix is /api and you have a collection called todos, the endpoints are at /api/todos.

Collection Endpoints

MethodPathDescription
GET/<prefix>/<slug>List records (with filtering, sorting, pagination)
GET/<prefix>/<slug>/:idGet a single record by ID
POST/<prefix>/<slug>Create a new record
PATCH/<prefix>/<slug>/:idUpdate a record
DELETE/<prefix>/<slug>/:idDelete a record

Authentication Endpoints

When a collection has auth: true, additional endpoints are generated:

MethodPathDescription
POST/<prefix>/<slug>/registerRegister a new user
POST/<prefix>/<slug>/loginLogin with email/password
POST/<prefix>/<slug>/refreshRefresh an access token
POST/<prefix>/<slug>/logoutLogout (revokes refresh token)
POST/<prefix>/<slug>/forgot-passwordRequest a password reset email
POST/<prefix>/<slug>/reset-passwordReset password with a token

Global Endpoints

Globals get two endpoints — one for reading, one for updating the singleton document:

MethodPathDescription
GET/<prefix>/globals/<slug>Get the global document
POST / PATCH/<prefix>/globals/<slug>Create or update the global document

System Endpoints

MethodPathDescription
GET/<prefix>/docsInteractive API docs (Scalar)
GET/<prefix>/docs/openapi.jsonOpenAPI specification
POST/<prefix>/uploadUpload a file
GET/<prefix>/uploads/:filenameServe an uploaded file
GET/<prefix>/wsWebSocket connection (if realtime is enabled)
GET/<prefix>/sseServer-Sent Events (if realtime is enabled)

Querying

Filter

Use where query parameter with JSON to filter records:

# Filter by exact match
GET /api/todos?where={"completed":{"eq":false}}

# Filter by multiple fields
GET /api/todos?where={"completed":{"eq":false},"priority":{"eq":"high"}}

# Using OR
GET /api/todos?where={"OR":[{"completed":{"eq":false}},{"priority":{"eq":"urgent"}}]}

Filter Operators

OperatorDescriptionExample
eqEquals{"title":{"eq":"Buy milk"}}
neqNot equals{"completed":{"neq":true}}
gtGreater than{"price":{"gt":100}}
gteGreater than or equal{"price":{"gte":100}}
ltLess than{"price":{"lt":50}}
lteLess than or equal{"price":{"lte":50}}
inIn array{"status":{"in":["draft","published"]}}
ninNot in array{"status":{"nin":["archived"]}}
likeLIKE pattern{"title":{"like":"%grocer%"}}
nlikeNOT LIKE{"title":{"nlike":"%test%"}}

Sort

Prefix with - for descending:

GET /api/todos?sort=-createdAt        # newest first
GET /api/todos?sort=priority           # ascending
GET /api/todos?sort=-priority,createdAt # multiple fields

Pagination

GET /api/todos?limit=10&page=1

Populate Relationships

Use depth to populate relationship fields:

# depth=0 (default): author is just the ID
GET /api/todos?depth=0

# depth=1: author is the full user object
GET /api/todos?depth=1

Response Format

List Response

{
  "docs": [
    { "id": "abc-123", "title": "Buy groceries", "completed": false, ... },
    { "id": "def-456", "title": "Walk the dog", "completed": true, ... }
  ],
  "totalDocs": 42,
  "limit": 10,
  "page": 1,
  "totalPages": 5,
  "hasNextPage": true,
  "hasPrevPage": false
}

Single Record

{
  "id": "abc-123",
  "title": "Buy groceries",
  "completed": false,
  "author": "user-uuid",
  "createdAt": "2026-07-01T12:00:00.000Z",
  "updatedAt": "2026-07-01T12:00:00.000Z"
}

Create Response

Status 201 Created — returns the created record.

Delete Response

{ "deleted": true }

Authentication

When JWT auth is enabled, include the access token in the Authorization header:

# Authenticated request
curl -H "Authorization: Bearer <accessToken>" http://localhost:3000/api/todos

Register

POST /api/users/register
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "securepassword",
  "name": "John Doe"
}

Response:

{
  "user": { "id": "abc-123", "email": "user@example.com", "name": "John Doe" },
  "accessToken": "eyJ...",
  "refreshToken": "eyJ...",
  "message": "Registration successful"
}

Login

POST /api/users/login
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "securepassword"
}

Refresh Token

POST /api/users/refresh
Content-Type: application/json

{ "refreshToken": "eyJ..." }

Forgot Password

POST /api/users/forgot-password
Content-Type: application/json

{ "email": "user@example.com" }

Always returns 200 with a generic message to prevent email enumeration.

Reset Password

POST /api/users/reset-password
Content-Type: application/json

{ "token": "reset-token-from-email", "password": "newpassword" }

Caching

When a collection has cache configured, GET responses include an X-Cache header:

  • X-Cache: HIT — served from cache
  • X-Cache: MISS — fetched from database (and cached for subsequent requests)

Cache is automatically invalidated on create, update, or delete operations for that collection.

Rate Limiting

When rate limiting is configured in security.rateLimit, requests exceeding the limit receive a 429 Too Many Requests response.

OpenAPI Documentation

Every Radiant server includes auto-generated API documentation at /<prefix>/docs (powered by Scalar). The OpenAPI JSON spec is available at /<prefix>/docs/openapi.json.

Custom routes with schema definitions and detail metadata are included in the spec.