Pages
Page management endpoints. Pages are static content entries such as "About Us", "Contact", or "Services". Unlike posts, pages support hierarchical structure through parent-child relationships and do not have categories or tags.
Base path: /api/v1/pages
List Pages
Required permission: pages:read
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
skip |
int | 0 |
Pagination offset |
limit |
int | 100 |
Pagination limit |
status |
string | — | Filter by status: draft, published, archived |
Response 200 OK
Get Page Tree
Returns the full page hierarchy as a nested tree structure. Intended for frontend navigation — no authentication required.
Authentication: Not required
Response 200 OK
[
{
"id": "uuid",
"slug": "services",
"status": "published",
"order": 0,
"parent_id": null,
"translations": [
{
"id": "uuid",
"page_id": "uuid",
"language_id": "uuid",
"title": "Services",
"content": null,
"excerpt": null,
"meta_title": null,
"meta_description": null,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": null
}
],
"children": [
{
"id": "uuid",
"slug": "web-development",
"status": "published",
"order": 0,
"parent_id": "uuid",
"translations": [
{
"id": "uuid",
"page_id": "uuid",
"language_id": "uuid",
"title": "Web Development",
"content": null,
"excerpt": null,
"meta_title": null,
"meta_description": null,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": null
}
],
"children": []
}
]
}
]
Info
The tree is sorted by the order field at every level.
Create Page
Required permission: pages:create
Request body
{
"slug": "about-us",
"status": "draft",
"parent_id": null,
"order": 0,
"featured_image_id": "uuid",
"author_id": "uuid",
"translations": [
{
"language_id": "uuid",
"title": "About Us",
"content": "<p>We are a company...</p>",
"excerpt": "Learn more about our company.",
"meta_title": "About Us - CMS",
"meta_description": "Learn more about our company and our mission."
}
]
}
| Field | Type | Required | Constraints |
|---|---|---|---|
slug |
string | ✅ | 2–255 characters, unique |
status |
string | ❌ | draft, published, archived. Default: draft |
parent_id |
UUID | ❌ | Must exist in DB |
order |
int | ❌ | Default: 0, min: 0 |
featured_image_id |
UUID | ❌ | Must exist in media |
author_id |
UUID | ❌ | Defaults to current user |
translations |
array | ❌ | List of translations |
Response 201 Created — full PageResponse object.
Errors
| Status | Description |
|---|---|
404 |
Parent page not found |
404 |
Featured image not found |
409 |
Page slug already exists |
Get Page
Required permission: pages:read
Response 200 OK
{
"id": "uuid",
"slug": "about-us",
"status": "published",
"parent_id": null,
"order": 0,
"featured_image_id": "uuid",
"author_id": "uuid",
"created_by": "uuid",
"translations": [
{
"id": "uuid",
"page_id": "uuid",
"language_id": "uuid",
"title": "About Us",
"content": "<p>We are a company...</p>",
"excerpt": "Learn more about our company.",
"meta_title": "About Us - CMS",
"meta_description": "Learn more about our company and our mission.",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": null
}
],
"featured_image": {
"id": "uuid",
"filename": "550e8400.jpg",
"original_filename": "team-photo.jpg",
"mime_type": "image/jpeg",
"size": 204800,
"path": "uploads/media/550e8400.jpg",
"alt_text": "Our team"
},
"created_at": "2026-01-01T00:00:00Z",
"updated_at": null
}
Errors
| Status | Description |
|---|---|
404 |
Page not found |
Get Page Children
Returns direct children of a page.
Required permission: pages:read
Response 200 OK — list of PageSummary objects.
Errors
| Status | Description |
|---|---|
404 |
Page not found |
Update Page
Required permission: pages:update
Request body — all fields optional
{
"slug": "about-us-updated",
"status": "published",
"parent_id": "uuid",
"order": 1,
"featured_image_id": "uuid",
"author_id": "uuid"
}
Response 200 OK — updated PageResponse object.
Status transitions
draft → published
draft → archived
published → draft
published → archived
archived → draft
archived → published
Errors
| Status | Description |
|---|---|
400 |
Page cannot be its own parent |
400 |
Circular page hierarchy is not allowed |
404 |
Page not found |
404 |
Parent page not found |
404 |
Featured image not found |
409 |
Page slug already exists |
Delete Page
Soft-deletes a page. Cannot delete a page that has active children.
Required permission: pages:delete
Response 204 No Content
Errors
| Status | Description |
|---|---|
400 |
Cannot delete page with active children |
404 |
Page not found |
Warning
Delete or reassign all children before deleting a parent page.
Upsert Translation
Creates or updates a translation for a specific language. If a translation for the given language already exists, it will be updated.
Required permission: pages:update
Request body
{
"language_id": "uuid",
"title": "About Us",
"content": "<p>We are a company...</p>",
"excerpt": "Learn more about our company.",
"meta_title": "About Us - CMS",
"meta_description": "Learn more about our company and our mission."
}
| Field | Type | Required | Description |
|---|---|---|---|
language_id |
UUID | ✅ | Must exist in DB |
title |
string | ✅ | Page title |
content |
string | ❌ | Full page content |
excerpt |
string | ❌ | Short summary |
meta_title |
string | ❌ | SEO title |
meta_description |
string | ❌ | SEO description |
Response 200 OK — updated PageResponse object.
Errors
| Status | Description |
|---|---|
404 |
Page not found |
404 |
Language not found |
Delete Translation
Removes a translation for a specific language from a page.
Required permission: pages:update
Response 200 OK — updated PageResponse object.
Errors
| Status | Description |
|---|---|
404 |
Page not found |
404 |
Translation not found |
Create Section
Adds a new section to a page. Section data is validated against the schema for the given type.
Required permission: pages:update
Request body
{
"type": "hero",
"order": 0,
"is_active": true,
"reference_id": null,
"translations": [
{
"language_id": "uuid",
"data": {
"title": "Welcome",
"subtitle": "We build great things",
"cta_text": "Learn more",
"cta_url": "/about"
}
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | ✅ | Section type — see supported types below |
order |
int | ❌ | Display order. Default: 0, min: 0 |
is_active |
bool | ❌ | Default: true |
reference_id |
UUID | ⚠️ | Required for slider, gallery, form types |
translations |
array | ❌ | List of translations with language-specific data |
Supported section types
| Type | data fields |
reference_id |
|---|---|---|
wysiwyg |
content |
— |
hero |
title, subtitle, cta_text, cta_url |
— |
cta |
title, subtitle, button_text, button_url |
— |
faq |
items[]{question, answer} |
— |
testimonials |
items[]{name, position, company, text} |
— |
gallery |
— | gallery_id |
slider |
— | slider_id |
form |
— | form_id |
Response 201 Created — PageSectionResponse object.
Errors
| Status | Description |
|---|---|
400 |
Invalid data for section type |
400 |
reference_id required for this section type |
404 |
Page not found |
404 |
Language not found |
Update Section
Required permission: pages:update
Request body — all fields optional
{
"order": 1,
"is_active": false,
"reference_id": "uuid",
"translations": [
{
"language_id": "uuid",
"data": {
"title": "Updated title"
}
}
]
}
translationsis an optional list — if provided, each entry is upserted by thesection_id + language_idcombination. Existing translations for a given language will be updated; if no translation exists for that language, a new record will be created.
Response 200 OK — updated PageSectionResponse object.
Errors
| Status | Description |
|---|---|
400 |
Invalid data for section type |
400 |
Section type does not use reference_id |
404 |
Page not found |
404 |
Section not found on this page |
404 |
Language not found |
Delete Section
Required permission: pages:delete
Response 204 No Content
Errors
| Status | Description |
|---|---|
404 |
Page not found |
404 |
Section not found on this page |
Reorder Sections
Updates the order field for multiple sections in a single request.
Required permission: pages:update
Request body
{
"sections": [
{ "id": "uuid", "order": 0 },
{ "id": "uuid", "order": 1 },
{ "id": "uuid", "order": 2 }
]
}
Response 204 No Content
Errors
| Status | Description |
|---|---|
404 |
Page not found |
404 |
Section not found on this page |
Get Section Types
Returns all available section types with their data schemas. Intended for frontend form generation — no authentication required.
Authentication: Not required
Response 200 OK
[
{
"type": "wysiwyg",
"label": "Rich Text",
"requires_reference": false,
"data_schema": {
"content": "string"
}
},
{
"type": "hero",
"label": "Hero Banner",
"requires_reference": false,
"data_schema": {
"title": "string",
"subtitle": "string?",
"cta_text": "string?",
"cta_url": "string?"
}
},
{
"type": "cta",
"label": "Call to Action",
"requires_reference": false,
"data_schema": {
"title": "string",
"subtitle": "string?",
"button_text": "string?",
"button_url": "string?"
}
},
{
"type": "faq",
"label": "FAQ",
"requires_reference": false,
"data_schema": {
"items": "[]{question: string, answer: string}"
}
},
{
"type": "testimonials",
"label": "Testimonials",
"requires_reference": false,
"data_schema": {
"items": "[]{name: string, position: string?, company: string?, text: string}"
}
},
{
"type": "gallery",
"label": "Gallery",
"requires_reference": true,
"data_schema": {}
},
{
"type": "slider",
"label": "Slider",
"requires_reference": true,
"data_schema": {}
},
{
"type": "form",
"label": "Form",
"requires_reference": true,
"data_schema": {}
}
]
Info
requires_reference: true means the section requires a reference_id pointing to an existing resource (gallery, slider, or form).