Content API

On this page

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 - Either posts or pages

Request Body:

{
    "action": "publish", // or "draft", "delete"
    "ids": ["1704067200000", "1704067300000", "1704067400000"]
}

Available Actions:

  • publish - Set status to published
  • draft - Set status to draft
  • delete - 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 []
    }
}