REST API Reference

REST API Reference

ModulaCMS exposes a JSON REST API at the base path /api/v1 for managing content, media, users, roles, and configuration programmatically.

Authentication

Every endpoint except public content delivery and the auth login/register routes requires authentication. The server checks two methods in this order:

  1. Session cookie -- Set automatically after a successful login or OAuth callback. You configure the cookie name in modula.config.json.
  2. API key -- When no valid session cookie is present, include a Bearer token in the Authorization header. The token must not be revoked or expired.
Authorization: Bearer 01HXK4N2F8RJZGP6VTQY3MCSW9

Common Patterns

Pattern Description
IDs All primary keys are 26-character ULID strings (e.g., "01HXK4N2F8RJZGP6VTQY3MCSW9")
Collection endpoint GET /api/v1/{resource} returns all items
Item endpoint /api/v1/{resource}/ operates on a single item
Item identification ?q={ulid} query parameter for GET, PUT, DELETE on item endpoints
Content-Type application/json for all request and response bodies
Timestamps RFC 3339 UTC (e.g., "2026-01-30T12:00:00Z")

Status Codes

Code Meaning
200 Success (GET, PUT, DELETE)
201 Created (POST)
204 No Content (DELETE with no body)
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
405 Method Not Allowed
409 Conflict (duplicate resource)
500 Internal Server Error

Auth Endpoints

The server rate limits auth endpoints to 10 requests per minute per IP and enables CORS on these routes.

Login

curl -X POST http://localhost:8080/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "admin@example.com", "password": "your-password"}'

Response (200):

{
  "user_id": "01HXK4N2F8RJZGP6VTQY3MCSW9",
  "email": "admin@example.com",
  "name": "Admin User"
}

The server sets a session cookie on success. Subsequent requests with that cookie skip the API key check.

Register

curl -X POST http://localhost:8080/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "new@example.com", "password": "secure-pass", "name": "New User"}'

Response (201):

{
  "user_id": "01HXK4N2F8RJZGP6VTQY3MCSW9",
  "email": "new@example.com",
  "name": "New User"
}

Logout

curl -X POST http://localhost:8080/api/v1/auth/logout \
  -H "Cookie: modula_cms=session-token-here"

Returns 200 on success and clears the session cookie.

Routes

Routes define URL paths that content attaches to.

List All Routes

curl http://localhost:8080/api/v1/routes \
  -H "Authorization: Bearer YOUR_API_KEY"

Response (200):

[
  {
    "route_id": "01HXK4N2F8RJZGP6VTQY3MCSW9",
    "slug": "/about",
    "title": "About Us",
    "status": 1,
    "author_id": "01HXK4N2F8RJZGP6VTQY3MCSW9",
    "date_created": "2026-01-30T12:00:00Z",
    "date_modified": "2026-01-30T12:00:00Z"
  }
]

Get Single Route

curl "http://localhost:8080/api/v1/routes/?q=01HXK4N2F8RJZGP6VTQY3MCSW9" \
  -H "Authorization: Bearer YOUR_API_KEY"

Create Route

curl -X POST http://localhost:8080/api/v1/routes \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"slug": "/about", "title": "About Us", "status": 1}'

Response (201): Returns the created route object.

Update Route

curl -X PUT "http://localhost:8080/api/v1/routes/?q=01HXK4N2F8RJZGP6VTQY3MCSW9" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "About Our Team"}'

Delete Route

curl -X DELETE "http://localhost:8080/api/v1/routes/?q=01HXK4N2F8RJZGP6VTQY3MCSW9" \
  -H "Authorization: Bearer YOUR_API_KEY"

Content Data

Content data entries hold the actual page content. Each entry belongs to a datatype and optionally a route.

List All Content

curl http://localhost:8080/api/v1/content \
  -H "Authorization: Bearer YOUR_API_KEY"

Get Single Content

curl "http://localhost:8080/api/v1/content/?q=CONTENT_DATA_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"

Create Content

curl -X POST http://localhost:8080/api/v1/content \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "datatype_id": "01HXK4...",
    "route_id": "01HXK4...",
    "status": "published",
    "author_id": "01HXK4..."
  }'

To create a child content entry, include parent_id instead of route_id.

Update Content

curl -X PUT "http://localhost:8080/api/v1/content/?q=CONTENT_DATA_ID" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "draft"}'

Delete Content

curl -X DELETE "http://localhost:8080/api/v1/content/?q=CONTENT_DATA_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"

Content Fields

Content fields store individual values (text, numbers, media references) attached to a content data entry.

List All Content Fields

curl http://localhost:8080/api/v1/content-fields \
  -H "Authorization: Bearer YOUR_API_KEY"

Get Single Content Field

curl "http://localhost:8080/api/v1/content-fields/?q=CONTENT_FIELD_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"

Create Content Field

curl -X POST http://localhost:8080/api/v1/content-fields \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content_data_id": "01HXK4...",
    "field_id": "01HXK4...",
    "field_value": "Hello, world!",
    "route_id": "01HXK4...",
    "author_id": "01HXK4..."
  }'

Update Content Field

curl -X PUT "http://localhost:8080/api/v1/content-fields/?q=CONTENT_FIELD_ID" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"field_value": "Updated value"}'

Delete Content Field

curl -X DELETE "http://localhost:8080/api/v1/content-fields/?q=CONTENT_FIELD_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"

Content Tree

The content tree endpoint assembles a complete content hierarchy for a route. This is the primary endpoint for frontend consumption.

Get Content Tree

curl "http://localhost:8080/api/v1/page/about?format=clean" \
  -H "Authorization: Bearer YOUR_API_KEY"

The format query parameter controls the response shape:

Format Description
clean Flat key-value fields, simplified structure
raw Full database representation with all IDs
contentful Contentful-compatible response shape
sanity Sanity-compatible response shape
strapi Strapi-compatible response shape
wordpress WordPress-compatible response shape

Example clean format response:

{
  "type": "documentation",
  "title": "Installation",
  "slug": "/docs/getting-started/installation",
  "children": [
    {
      "type": "doc_section",
      "heading": "Installation",
      "content": "## Prerequisites\n\nYou need Go 1.21 or later..."
    }
  ]
}

Datatypes

Datatypes define the schema for content entries.

List Datatypes

curl http://localhost:8080/api/v1/datatypes \
  -H "Authorization: Bearer YOUR_API_KEY"

Create Datatype

curl -X POST http://localhost:8080/api/v1/datatypes \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "Blog Post", "type": "content", "name": "blog_post"}'

Fields

Fields define the individual data points within a datatype.

List Fields

curl http://localhost:8080/api/v1/fields \
  -H "Authorization: Bearer YOUR_API_KEY"

Create Field

curl -X POST http://localhost:8080/api/v1/fields \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Body",
    "field_type": "richtext",
    "parent_id": "DATATYPE_ID"
  }'

Supported field types: text, textarea, number, date, datetime, boolean, select, media, relation, json, richtext, slug, email, url.

Media

List Media

curl http://localhost:8080/api/v1/media \
  -H "Authorization: Bearer YOUR_API_KEY"

Upload Media

curl -X POST http://localhost:8080/api/v1/media/upload \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@photo.jpg" \
  -F "alt_text=A photo" \
  -F "folder_id=FOLDER_ID"

Delete Media

curl -X DELETE "http://localhost:8080/api/v1/media/?q=MEDIA_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"

Users

List Users

curl http://localhost:8080/api/v1/users \
  -H "Authorization: Bearer YOUR_API_KEY"

Create User

curl -X POST http://localhost:8080/api/v1/users \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "editor@example.com",
    "password": "secure-pass",
    "name": "Editor",
    "role_id": "ROLE_ID"
  }'

Roles and Permissions

List Roles

curl http://localhost:8080/api/v1/roles \
  -H "Authorization: Bearer YOUR_API_KEY"

Create Role

curl -X POST http://localhost:8080/api/v1/roles \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "Editor"}'

Assign Permission to Role

curl -X POST http://localhost:8080/api/v1/role-permissions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"role_id": "ROLE_ID", "permission_id": "PERM_ID"}'

Tokens

API tokens provide programmatic access without session cookies.

List Tokens

curl http://localhost:8080/api/v1/tokens \
  -H "Authorization: Bearer YOUR_API_KEY"

Create Token

curl -X POST http://localhost:8080/api/v1/tokens \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "CI/CD Pipeline", "user_id": "USER_ID"}'

Response (201):

{
  "token_id": "01HXK4...",
  "token": "mcms_abc123...",
  "label": "CI/CD Pipeline",
  "user_id": "01HXK4...",
  "date_created": "2026-01-30T12:00:00Z"
}

Store the token value immediately. The server only returns the full token at creation time.

Revoke Token

curl -X DELETE "http://localhost:8080/api/v1/tokens/?q=TOKEN_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"

Configuration

Get Config

curl http://localhost:8080/api/v1/config \
  -H "Authorization: Bearer YOUR_API_KEY"

Update Config

curl -X PUT http://localhost:8080/api/v1/config \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cookie_name": "my_cms", "cookie_duration": "720h"}'

Error Responses

All error responses follow this shape:

{
  "error": "description of what went wrong"
}

The HTTP status code indicates the error category. The error field provides a human-readable message suitable for logging but not for end-user display.