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:
- Session cookie -- Set automatically after a successful login or OAuth callback. You configure the cookie name in
modula.config.json. - API key -- When no valid session cookie is present, include a Bearer token in the
Authorizationheader. 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.