Content API
Overview
The Content API provides comprehensive endpoints for managing posts, pages, and site settings in Aether CMS. All content is stored in Markdown format with YAML frontmatter for metadata.
Posts API
Get All Posts
GET /api/posts
Query Parameters:
status
- Filter by status (published
,draft
)limit
- Number of posts to return (default: 10)offset
- Number of posts to skip (default: 0)frontmatterOnly
- Return only metadata (true
/false
)properties
- Comma-separated list of properties to return
Examples:
// Get published posts
const response = await fetch("/api/posts?status=published&limit=5")
// Get only metadata
const response = await fetch("/api/posts?frontmatterOnly=true&properties=id,title,slug")
Success Response (200):
{
"success": true,
"data": [
{
"frontmatter": {
"id": "1704067200000",
"title": "My First Post",
"subtitle": "Subtitle of my first post",
"slug": "my-first-post",
"status": "published",
"author": "admin",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z",
"category": "Technology",
"tags": ["JavaScript", "API"],
"excerpt": "This is a brief excerpt of the post...",
"featuredImage": {
"id": "img_123",
"url": "/images/featured.jpg",
"alt": "Featured image"
}
},
"content": "# Post Content\n\nThis is the full markdown content..."
}
]
}
Get Single Post
GET /api/posts/{id}
Query Parameters:
resolveRelated
- Include related posts data (true
/false
, default:true
)
Success Response (200):
{
"success": true,
"data": {
"id": "1704067200000",
"title": "My First Post",
"subtitle": "Subtitle of my first post",
"slug": "my-first-post",
"content": "# Post Content\n\nMarkdown content here...",
"status": "published",
"author": "admin",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z",
"category": "Technology",
"tags": ["JavaScript", "API"],
"relatedPosts": ["1704067300000", "1704067400000"],
"relatedPostsData": [
{
"id": "1704067300000",
"subtitle": "Subtitle of related post",
"title": "Related Post",
"slug": "related-post",
"featuredImage": {
"id": "img_456",
"url": "/images/featured.png",
"alt": "Featured image"
},
"excerpt": "Brief excerpt..."
}
]
}
}
Create Post
POST /api/posts
Request Body:
{
"metadata": {
"title": "New Post Title",
"subtitle": "Subtitle of new post",
"slug": "new-post-title",
"status": "draft",
"author": "admin",
"category": "Technology",
"tags": ["JavaScript", "Tutorial"],
"excerpt": "Brief description of the post",
"featuredImage": {
"id": "img_456",
"url": "/images/new-featured.jpg",
"alt": "New featured image"
},
"relatedPosts": ["1704067200000"]
},
"content": "# New Post\n\nThis is the content of the new post..."
}
Success Response (201):
{
"success": true,
"id": "1704153600000"
}
Error Response (409 - Duplicate Slug):
{
"success": false,
"error": "A post with this slug already exists",
"code": "DUPLICATE_SLUG",
"slug": "new-post-title"
}
Update Post
PUT /api/posts/{id}
Request Body: Same format as create post
Success Response (200):
{
"success": true,
"data": {
"id": "1704067200000",
"title": "Updated Post Title",
"slug": "updated-post-title",
"status": "published",
"updatedAt": "2024-01-01T12:00:00.000Z"
}
}
Delete Post
DELETE /api/posts/{id}
Success Response (200):
{
"success": true
}
Pages API
Get All Pages
GET /api/pages
Query Parameters:
status
- Filter by status (published
,draft
)pageType
- Filter by type (normal
,custom
)
Success Response (200):
{
"success": true,
"data": [
{
"frontmatter": {
"id": "page_001",
"title": "About Us",
"subtitle": "Get to Know Who We Are and What Drives Us",
"slug": "about",
"status": "published",
"pageType": "normal",
"createdAt": "2024-01-01T00:00:00.000Z",
"parentPage": null
},
"content": "# About Us\n\nPage content here..."
}
]
}
Get Single Page
GET /api/pages/{id}
Similar to posts, returns full page data with content.
Create Page
POST /api/pages
Request Body:
{
"metadata": {
"title": "New Page",
"subtitle": "Subtitle of new page",
"slug": "new-page",
"status": "published",
"pageType": "custom",
"parentPage": "documentation"
},
"content": "# New Page\n\nPage content here..."
}
Validation Rules:
- Parent pages must exist and be custom pages
- No circular parent relationships
- Unique slugs within page type
Update Page
PUT /api/pages/{id}
Same format as create page, with additional validation for parent relationships.
Delete Page
DELETE /api/pages/{id}
Success Response (200):
{
"success": true
}
Bulk Operations
Bulk Actions for Posts/Pages
POST /api/bulk/{contentType}
Path Parameters:
contentType
- Eitherposts
orpages
Request Body:
{
"action": "publish", // or "draft", "delete"
"ids": ["1704067200000", "1704067300000", "1704067400000"]
}
Available Actions:
publish
- Set status to publisheddraft
- Set status to draftdelete
- Permanently delete items
Success Response (200):
{
"success": true,
"results": [
{
"id": "1704067200000",
"success": true,
"result": { "id": "1704067200000", "status": "published" }
},
{
"id": "1704067300000",
"success": false,
"error": "Post not found"
}
],
"errors": [
{
"id": "1704067300000",
"error": "Post not found"
}
],
"totalItems": 2,
"successCount": 1,
"errorCount": 1,
"message": "Applied \"publish\" with 1 errors. See details in results."
}
Bulk Operation Features:
- Processes items in batches of 25
- Continues processing even if individual items fail
- Provides detailed success/failure reporting
- Includes progress information for large operations
Site Settings API
Get Site Settings
GET /api/settings
Success Response (200):
{
"success": true,
"data": {
"siteTitle": "My Aether Site",
"siteDescription": "A site built with Aether",
"postsPerPage": 10,
"activeTheme": "default",
"footerCode": "Content in Motion. Powered by Aether.",
"siteUrl": "https://mysite.com",
"staticOutputDir": "_site",
"staticBaseUrl": "/",
"staticCleanUrls": true
}
}
Update Site Settings
PUT /api/settings
Request Body:
{
"siteTitle": "Updated Site Title",
"siteDescription": "Updated description",
"postsPerPage": 15,
"siteUrl": "https://newdomain.com"
}
Success Response (200):
{
"success": true,
"data": {
"siteTitle": "Updated Site Title",
"siteDescription": "Updated description",
"postsPerPage": 15,
"siteUrl": "https://newdomain.com",
"activeTheme": "default"
}
}
Content Optimization
Query Optimization
The API supports content optimization through query parameters:
Frontmatter Only:
// Returns only metadata, no content body
const response = await fetch("/api/posts?frontmatterOnly=true")
Property Selection:
// Returns only specified properties
const response = await fetch("/api/posts?frontmatterOnly=true&properties=id,title,slug,createdAt")
Summary View:
// Returns truncated content preview
const response = await fetch("/api/posts?summaryView=true&previewLength=200")
Content Relationships
Related Posts
Posts can reference other posts through the relatedPosts
array:
{
"metadata": {
"relatedPosts": ["post_id_1", "post_id_2", "post_id_3"]
}
}
When retrieving posts, related post data is automatically resolved:
{
"relatedPostsData": [
{
"id": "post_id_1",
"title": "Related Post Title",
"subtitle": "RElated Post Subtitle",
"slug": "related-post-slug",
"featuredImage": {
"id": "img_789",
"url": "/images/related-featured.avif",
"alt": "Related post featured image"
},
"excerpt": "Brief excerpt..."
}
]
}
Page Hierarchies
Custom pages support parent-child relationships:
{
"metadata": {
"pageType": "custom",
"parentPage": "documentation",
"slug": "api-guide"
}
}
This creates a hierarchy: /documentation/api-guide
Error Handling
Common Error Responses
400 Bad Request:
{
"success": false,
"error": "Title is required"
}
404 Not Found:
{
"success": false,
"error": "Post not found"
}
409 Conflict:
{
"success": false,
"error": "A post with this slug already exists",
"code": "DUPLICATE_SLUG",
"slug": "conflicting-slug"
}
Validation Rules
Posts:
- Title is required
- Slug must be unique within posts
- Author defaults to current user
- Status defaults to "draft"
Pages:
- Title is required
- Slug must be unique within page type
- Parent pages must exist and be custom pages
- No circular parent relationships
Usage Examples
Creating a Blog Post
async function createBlogPost(postData) {
try {
const response = await fetch("/api/posts", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
metadata: {
title: postData.title,
slug: postData.slug,
status: "published",
category: postData.category,
tags: postData.tags,
excerpt: postData.excerpt,
featuredImage: postData.featuredImage,
},
content: postData.content,
}),
})
const result = await response.json()
if (result.success) {
console.log("Post created with ID:", result.id)
return result.id
} else {
throw new Error(result.error)
}
} catch (error) {
console.error("Failed to create post:", error)
throw error
}
}
Fetching Posts with Optimization
async function getPostsOptimized() {
try {
const response = await fetch(
"/api/posts?status=published&frontmatterOnly=true&properties=id,title,slug,createdAt,excerpt&limit=20",
{
headers: {
Authorization: `Bearer ${token}`,
},
}
)
const result = await response.json()
if (result.success) {
return result.data.map((post) => ({
id: post.frontmatter.id,
title: post.frontmatter.title,
slug: post.frontmatter.slug,
createdAt: post.frontmatter.createdAt,
excerpt: post.frontmatter.excerpt,
}))
}
} catch (error) {
console.error("Failed to fetch posts:", error)
return []
}
}