Docs

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.ai

Authentication

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 levelAuth required
Published content (GET)No
Drafts, preview, activityYes
Write operations (POST, PATCH, DELETE)Yes
Roadmap votingNo

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
ParameterTypeDescription
limitnumberMax results (default 20, max 100)
tagstringFilter by tag
cursorstringISO date for cursor-based pagination

Returns { posts, next_cursor, total }.

List drafts

GET /blog/drafts

Requires auth. Returns posts with status draft or review.

Get a post

GET /blog/:slug
ParameterTypeDescription
previewbooleanInclude 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"
}
FieldTypeRequiredDescription
titlestringYesPost title
contentstringNoMarkdown body
descriptionstringNoSEO description (50-160 chars)
author_namestringNoAuthor name (default: Moonage Team)
tagsstring[]NoTag array
statusstringNodraft or published (default: draft)
og_image_urlstringNoOpen Graph image URL

Update a post

PATCH /blog/:slug

Accepts 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.

EndpointTransitionNotes
POST /blog/:slug/submitdraft → reviewValidates title, description, content length, and tags
POST /blog/:slug/approvereview → publishedAgents blocked unless AGENT_CAN_PUBLISH=true
POST /blog/:slug/publishany → publishedDirect publish, skips review
POST /blog/:slug/archivepublished → archivedOnly published posts can be archived

Delete a post

DELETE /blog/:slug

Hard delete. Only drafts can be deleted.


Changelog

List published entries

GET /changelog
ParameterTypeDescription
limitnumberMax results (default 20, max 100)
typestringFilter: release, hotfix, beta, alpha
cursorstringISO date for pagination

Returns { entries, next_cursor, total }.

List drafts

GET /changelog/drafts

Requires auth.

Get an entry

GET /changelog/:identifier

Looks up by slug or version string (e.g. v2.3.0).

ParameterTypeDescription
previewbooleanInclude 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"
}
FieldTypeRequiredDescription
versionstringYesVersion string (e.g. v2.3.0)
titlestringNoEntry title (default: "Release {version}")
summarystringNoShort summary
contentstringNoFull markdown content
typestringNorelease, hotfix, beta, alpha
highlightsstring[]NoKey highlights
statusstringNodraft or published

Update an entry

PATCH /changelog/:identifier

Accepts: title, summary, content, type, highlights.

Editorial workflow

EndpointTransition
POST /changelog/:identifier/submitdraft → review
POST /changelog/:identifier/approvereview → published
POST /changelog/:identifier/publishany → published

Roadmap

List public items

GET /roadmap
ParameterTypeDescription
limitnumberMax results (default 50, max 100)
statusstringplanned, in_progress, shipped, cancelled, considering, or all
categorystringproduct, platform, integrations, api, dx
quarterstringFilter by quarter (e.g. Q2 2026)
sortstringpriority (default), votes, updated_at
cursorstringISO date for pagination

Returns { items, next_cursor, total, by_status }.

List all items (internal)

GET /roadmap/all

Requires auth. Includes non-public items.

Get an item

GET /roadmap/:slug

Create 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"
}
FieldTypeRequiredDescription
titlestringYesItem title
descriptionstringNoFull markdown description
summarystringNoShort summary
categorystringNoproduct, platform, integrations, api, dx
statusstringNoplanned, in_progress, shipped, cancelled, considering
prioritystringNolow, medium, high, critical
quarterstringNoTarget quarter
publicbooleanNoVisible on public roadmap (default: true)

Update an item

PATCH /roadmap/:slug

Accepts: title, description, summary, category, status, priority, quarter, public.

Ship an item

POST /roadmap/:slug/ship

Sets 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/upload

Multipart form upload to the R2 bucket.

FieldTypeDescription
fileFileImage file (required)
typestringinline or og (default: inline)
slugstringResource 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/:key

Deletes the asset from R2. The key is the full path (e.g. inline/my-post-1709123456.png).


Search across content

GET /search
ParameterTypeDescription
querystringSearch term (required)
typestringall, blog, changelog, or roadmap (default: all)
limitnumberMax 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 /activity

Requires auth. Returns a chronological log of all content changes.

ParameterTypeDescription
resourcestringFilter: blog, changelog, roadmap
resource_idstringFilter by specific resource ID
actor_typestringFilter: human, agent, system
actionstringFilter: created, updated, published, status_changed, archived, bulk_updated
afterstringISO date — entries after this time
beforestringISO date — entries before this time
limitnumberMax entries (default 50, max 200)
cursorstringISO date for pagination

Returns { entries, next_cursor }.


Revalidation

POST /revalidate

Triggers 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 /health

Returns service status:

{ "ok": true, "service": "moonage-content", "mcp": "/mcp" }