API Reference

Get API key
REST API · v1

owlCMS API

The owlCMS API lets you programmatically access your content. Use it to build websites, mobile apps, or integrate with any service. All responses are JSON.

Overview

The owlCMS API is a RESTful JSON API following a headless architecture — we manage your data while you maintain full control over your frontend design and implementation.

Base URL

https://api.owlcms.io/v1

Best Practices & SEO

Choosing the right rendering strategy is critical for performance and search visibility.

SSG — Static Site Generation

Recommended

Used by: Astro, Next.js (Static), Hugo, Jekyll. Your site is built once at build time. It fetches all posts from owlCMS and generates a real .html file for every post. Since the HTML already contains your content, Google sees it instantly. Fastest and most SEO-friendly.

Pro tip: Use SSG + Webhooks — save a post in owlCMS → webhook triggers a rebuild → your site becomes lightning-fast static files.

SSR — Server-Side Rendering

Dynamic

Used by: Next.js, Remix, Nuxt, SvelteKit. The server generates HTML on every request, fetching fresh data from owlCMS each time. Best when your content changes every few minutes and you can't wait for a build step.

CSR — Client-Side Rendering

Not for SEO

Used by: Plain Vite, Create React App, Vue CLI. The server sends a blank HTML file and a JS bundle. The browser fetches data and builds the page — but search crawlers often see a blank screen.

Social media platforms (Facebook, Twitter) cannot read your post titles or images. Avoid CSR for any public content.

Sitemaps

owlCMS is a headless API — your frontend is responsible for generating sitemap.xml. This section shows how to do it correctly at any scale.

1. Paginate through all posts

The /v1/posts endpoint returns up to 100 posts per request. If you have more, loop with offset until posts.length === pagination.total. Each post already includes updatedAt — use it as <lastmod> so Google knows which pages changed since the last crawl.

// Fetch every published post using pagination
async function getAllPosts(apiKey) {
  const posts = [];
  const limit = 100;
  let offset = 0;

  while (true) {
    const res = await fetch(
      `https://api.owlcms.io/v1/posts?limit=${limit}&offset=${offset}`,
      { headers: { 'X-API-Key': apiKey } }
    );
    const { data, pagination } = await res.json();
    posts.push(...data);

    if (posts.length >= pagination.total) break;
    offset += limit;
  }

  return posts; // array of all published posts
}

2. Generate the XML — Astro

Create a dynamic route that fetches all posts at build time (SSG) or on each request (SSR) and streams back valid XML. Astro, Nuxt, and SvelteKit all support this pattern natively.

// Astro: src/pages/sitemap.xml.ts
import type { APIRoute } from 'astro';

export const GET: APIRoute = async () => {
  const posts = await getAllPosts(import.meta.env.OWL_API_KEY);

  const urls = posts.map((post) => `
  <url>
    <loc>https://yoursite.com/blog/${post.slug}</loc>
    <lastmod>${new Date(post.updatedAt).toISOString()}</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.7</priority>
  </url>`).join('');

  return new Response(
    `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls}
</urlset>`,
    { headers: { 'Content-Type': 'application/xml' } }
  );
};

2. Generate the XML — Next.js App Router

Next.js 13+ has a built-in sitemap.ts convention that handles the XML formatting for you.

// Next.js App Router: app/sitemap.ts
import type { MetadataRoute } from 'next';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const posts = await getAllPosts(process.env.OWL_API_KEY!);

  return posts.map((post) => ({
    url: `https://yoursite.com/blog/${post.slug}`,
    lastModified: new Date(post.updatedAt),
    changeFrequency: 'weekly',
    priority: 0.7,
  }));
}

3. Thousands of posts — use a sitemap index

Google's limit is 50,000 URLs per sitemap file (and 50 MB uncompressed). Once you exceed ~5,000 posts we recommend splitting into multiple sitemaps and referencing them from a sitemap-index.xml. Generate each child sitemap with a fixed offset window (e.g. 0–999, 1000–1999, …).

<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>https://yoursite.com/sitemap-posts-1.xml</loc>  <!-- slugs 0–999 -->
    <lastmod>2025-01-15T10:00:00Z</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://yoursite.com/sitemap-posts-2.xml</loc>  <!-- slugs 1000–1999 -->
    <lastmod>2025-01-15T10:00:00Z</lastmod>
  </sitemap>
</sitemapindex>

4. Keep the sitemap fresh automatically

For SSG sites the sitemap is stale the moment you publish a new post. The fix is simple: set a Webhook URL on your page (in the Pages dashboard) and point it at your deployment provider's deploy hook. Every time owlCMS creates a post the webhook fires, your build triggers, and your sitemap regenerates automatically with the latest updatedAt timestamps. See the Webhooks section for deploy hook setup instructions.

Don't forget robots.txt

Point Google to your sitemap by adding Sitemap: https://yoursite.com/sitemap.xml to your robots.txt. Then submit the URL in Google Search Console → Sitemaps for faster indexing.

Authentication

All API requests require an API key. Create and manage keys from your Dashboard Settings.

Recommended

X-API-Key header

X-API-Key: owlcms_your_api_key_here

Alternative

Bearer token

Authorization: Bearer owlcms_your_api_key_here

Keep your API keys secure

Never expose keys in client-side code or public repositories. Always use environment variables on your server or build process.

Endpoints

GET/v1/posts

Retrieve a paginated list of all published posts. Each post includes a computed excerpt (plain-text preview, ≤160 chars) and coverImage field.

GET/v1/posts/:id

Retrieve a single post by its unique MongoDB ObjectId.

GET/v1/posts/slug/:slug

Retrieve a single post by its URL slug. Ideal for building frontend routes like /blog/:slug.

GET/v1/posts/render/:slug

Returns a post with a pre-rendered html field. Perfect for quick integrations without needing a content parser.

Webhooks & Deployment

Webhooks allow owlCMS to notify your website whenever content changes. Essential for triggering automatic rebuilds of static sites. Webhooks are configured per page in the Pages dashboard — each page can have its own webhook URL.

Payload format

When an event occurs, owlCMS sends a POST request to your configured URL:

{
  "event": "post.created",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "data": {
    "id": "64f8a1b2c3d4e5f6a7b8c9d0",
    "title": "My New Post",
    "slug": "my-new-post"
  }
}

Verifying signatures

If you set a webhook secret on a page, owlCMS signs every payload with HMAC-SHA256 and includes the signature in the X-Webhook-Signature header:

X-Webhook-Signature: sha256=<hex-digest>

// Verify in Node.js
import { createHmac } from 'crypto';
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
if (req.headers['x-webhook-signature'] !== expected) throw new Error('Invalid signature');

Automatic rebuilds — deploy hook setup

Generate a deploy hook URL from your hosting provider, then paste it into the Webhook URL field on the relevant page in the Pages dashboard. Every time owlCMS publishes a post it will call that URL and trigger a rebuild.

Vercel — Settings → Git → Deploy Hooks

Netlify — Site Settings → Build & Deploy → Build hooks

Cloudflare Pages — Settings → Builds & deployments → Deploy hooks

Error Codes

Standard HTTP status codes indicate success or failure.

CodeStatusDescription
200OKRequest succeeded
401UnauthorizedMissing or invalid API key
404Not FoundRequested resource doesn't exist
429Too Many RequestsRate limit exceeded
500Server ErrorInternal error on our end

401 Unauthorized

{ "error": "Unauthorized", "message": "Valid API key required" }

404 Not Found

{ "error": "Not Found", "message": "Post not found" }

Quick Start

Get your first API response in under two minutes.

1

Get your API key

Log in to your Dashboard, go to Settings, and create a new key in the API Keys section.

2

Fetch all posts

Call the list endpoint to retrieve your published posts.

// All posts
const response = await fetch('https://api.owlcms.io/v1/posts', {
  headers: { 'X-API-Key': 'owlcms_your_api_key_here' }
});

// Filter by page slug
const response = await fetch('https://api.owlcms.io/v1/posts?page=my-blog', {
  headers: { 'X-API-Key': 'owlcms_your_api_key_here' }
});
const data = await response.json();
console.log(data.data); // Array of posts
3

Render a single post

Use the slug endpoint to retrieve and display individual posts in your frontend.

const response = await fetch('https://api.owlcms.io/v1/posts/render/my-post', {
  headers: {
    'X-API-Key': 'owlcms_your_api_key_here'
  }
});
const { data: post } = await response.json();
// 'post.html' now contains a pre-rendered HTML string
document.getElementById('content').innerHTML = post.html;

Need help?

Contact our support team or check the community forum for integration examples.