Content API
REST API for managing blog posts, changelog entries, roadmap items, assets, and activity logs.
The Content API powers the Moonage website content pipeline. It provides full CRUD operations with a built-in editorial workflow, asset management via R2, and a cross-content search endpoint.
Base URL
https://content.moonage.aiAuthentication
Write operations, drafts, preview mode, and the activity log require a Bearer token:
curl -H "Authorization: Bearer YOUR_CONTENT_API_SECRET" \
https://content.moonage.ai/blog| Access level | Auth required |
|---|---|
| Published content (GET) | No |
| Drafts, preview, activity | Yes |
| Write operations (POST, PATCH, DELETE) | Yes |
| Roadmap voting | No |
Actor identity
All write requests must include an actor in the request body:
{
"actor_id": "user_abc",
"actor_type": "human"
}Actor types: human, agent, system.
Blog posts
List published posts
GET /blog| Parameter | Type | Description |
|---|---|---|
limit | number | Max results (default 20, max 100) |
tag | string | Filter by tag |
cursor | string | ISO date for cursor-based pagination |
Returns { posts, next_cursor, total }.
List drafts
GET /blog/draftsRequires auth. Returns posts with status draft or review.
Get a post
GET /blog/:slug| Parameter | Type | Description |
|---|---|---|
preview | boolean | Include non-published posts (requires auth) |
Create a post
POST /blog{
"title": "Getting Started with Moonage",
"content": "## Introduction\n\nMarkdown content here...",
"description": "Learn how to set up your first Space.",
"author_name": "Moonage Team",
"tags": ["tutorial", "getting-started"],
"status": "draft",
"actor_id": "user_abc",
"actor_type": "human"
}| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Post title |
content | string | No | Markdown body |
description | string | No | SEO description (50-160 chars) |
author_name | string | No | Author name (default: Moonage Team) |
tags | string[] | No | Tag array |
status | string | No | draft or published (default: draft) |
og_image_url | string | No | Open Graph image URL |
Update a post
PATCH /blog/:slugAccepts any of: title, content, description, author_name, author_email, tags, og_image_url.
Editorial workflow
Blog posts follow a draft → review → published workflow. Posts can also be directly published or archived.
| Endpoint | Transition | Notes |
|---|---|---|
POST /blog/:slug/submit | draft → review | Validates title, description, content length, and tags |
POST /blog/:slug/approve | review → published | Agents blocked unless AGENT_CAN_PUBLISH=true |
POST /blog/:slug/publish | any → published | Direct publish, skips review |
POST /blog/:slug/archive | published → archived | Only published posts can be archived |
Delete a post
DELETE /blog/:slugHard delete. Only drafts can be deleted.
Changelog
List published entries
GET /changelog| Parameter | Type | Description |
|---|---|---|
limit | number | Max results (default 20, max 100) |
type | string | Filter: release, hotfix, beta, alpha |
cursor | string | ISO date for pagination |
Returns { entries, next_cursor, total }.
List drafts
GET /changelog/draftsRequires auth.
Get an entry
GET /changelog/:identifierLooks up by slug or version string (e.g. v2.3.0).
| Parameter | Type | Description |
|---|---|---|
preview | boolean | Include non-published entries (requires auth) |
Create an entry
POST /changelog{
"version": "v2.3.0",
"title": "Release v2.3.0",
"summary": "New integrations and performance improvements",
"content": "## What's new\n\n...",
"type": "release",
"highlights": ["GitHub integration", "50% faster search"],
"status": "draft",
"actor_id": "user_abc",
"actor_type": "human"
}| Field | Type | Required | Description |
|---|---|---|---|
version | string | Yes | Version string (e.g. v2.3.0) |
title | string | No | Entry title (default: "Release {version}") |
summary | string | No | Short summary |
content | string | No | Full markdown content |
type | string | No | release, hotfix, beta, alpha |
highlights | string[] | No | Key highlights |
status | string | No | draft or published |
Update an entry
PATCH /changelog/:identifierAccepts: title, summary, content, type, highlights.
Editorial workflow
| Endpoint | Transition |
|---|---|
POST /changelog/:identifier/submit | draft → review |
POST /changelog/:identifier/approve | review → published |
POST /changelog/:identifier/publish | any → published |
Roadmap
List public items
GET /roadmap| Parameter | Type | Description |
|---|---|---|
limit | number | Max results (default 50, max 100) |
status | string | planned, in_progress, shipped, cancelled, considering, or all |
category | string | product, platform, integrations, api, dx |
quarter | string | Filter by quarter (e.g. Q2 2026) |
sort | string | priority (default), votes, updated_at |
cursor | string | ISO date for pagination |
Returns { items, next_cursor, total, by_status }.
List all items (internal)
GET /roadmap/allRequires auth. Includes non-public items.
Get an item
GET /roadmap/:slugCreate an item
POST /roadmap{
"title": "GitHub Actions integration",
"description": "Full markdown description...",
"summary": "Connect CI/CD pipelines to Spaces",
"category": "integrations",
"status": "planned",
"priority": "high",
"quarter": "Q2 2026",
"public": true,
"actor_id": "user_abc",
"actor_type": "human"
}| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Item title |
description | string | No | Full markdown description |
summary | string | No | Short summary |
category | string | No | product, platform, integrations, api, dx |
status | string | No | planned, in_progress, shipped, cancelled, considering |
priority | string | No | low, medium, high, critical |
quarter | string | No | Target quarter |
public | boolean | No | Visible on public roadmap (default: true) |
Update an item
PATCH /roadmap/:slugAccepts: title, description, summary, category, status, priority, quarter, public.
Ship an item
POST /roadmap/:slug/shipSets status to shipped and records shipped_at. Accepts optional note and changelog_version.
Bulk update
POST /roadmap/bulk{
"items": [
{ "slug": "github-actions", "status": "shipped" },
{ "slug": "linear-sync", "quarter": "Q3 2026", "priority": "high" }
],
"actor_id": "user_abc",
"actor_type": "human"
}Each item can update status, quarter, and priority.
Voting
Public endpoints — no auth required.
POST /roadmap/:slug/vote{ "voter_id": "visitor_xyz" }Returns { votes, voted: true }. Idempotent — duplicate votes are ignored.
DELETE /roadmap/:slug/vote{ "voter_id": "visitor_xyz" }Returns { votes, voted: false }.
Assets
Upload an image
POST /assets/uploadMultipart form upload to the R2 bucket.
| Field | Type | Description |
|---|---|---|
file | File | Image file (required) |
type | string | inline or og (default: inline) |
slug | string | Resource slug for naming (optional) |
Constraints: PNG, JPEG, or WebP only. Max 5 MB.
Returns the public URL and storage key:
{
"url": "https://assets.moonage.ai/inline/my-post-1709123456.png",
"key": "inline/my-post-1709123456.png"
}Delete an asset
DELETE /assets/:keyDeletes the asset from R2. The key is the full path (e.g. inline/my-post-1709123456.png).
Search
Search across content
GET /search| Parameter | Type | Description |
|---|---|---|
query | string | Search term (required) |
type | string | all, blog, changelog, or roadmap (default: all) |
limit | number | Max results per type (default 10, max 50) |
Searches title, description/summary, and content body using LIKE matching.
{
"results": {
"blog": [{ "slug": "...", "title": "...", "description": "...", "type": "blog" }],
"changelog": [],
"roadmap": [{ "slug": "...", "title": "...", "description": "...", "type": "roadmap" }]
}
}Activity log
GET /activityRequires auth. Returns a chronological log of all content changes.
| Parameter | Type | Description |
|---|---|---|
resource | string | Filter: blog, changelog, roadmap |
resource_id | string | Filter by specific resource ID |
actor_type | string | Filter: human, agent, system |
action | string | Filter: created, updated, published, status_changed, archived, bulk_updated |
after | string | ISO date — entries after this time |
before | string | ISO date — entries before this time |
limit | number | Max entries (default 50, max 200) |
cursor | string | ISO date for pagination |
Returns { entries, next_cursor }.
Revalidation
POST /revalidateTriggers Next.js ISR revalidation for a specific content page. Called automatically on publish/update, but can be triggered manually.
{
"type": "blog",
"slug": "getting-started"
}Health check
GET /healthReturns service status:
{ "ok": true, "service": "moonage-content", "mcp": "/mcp" }