---
name: aiblogs-headless-cms
description: Integrate AI Blogs headless CMS into any web, mobile, or backend application (Next.js, React, Astro, Remix, Vue, SvelteKit, Python, Swift, Kotlin). Use when querying posts, fetching articles by slug, managing categories/tags, rendering SEO JSON-LD schema, or building programmatic publishing workflows with AI Blogs APIs.
---

# AI Blogs Headless CMS — Integration & Automation Skill

This skill provides complete instructions, API contracts, TypeScript types, framework recipes, and SEO patterns for LLMs and AI coding assistants to integrate **AI Blogs** into any codebase.

---

## 1. Overview & Architecture

**AI Blogs** provides a multi-tenant, edge-ready headless CMS with two primary API layers:

1. **Headless Delivery API (`/api/v1/public/*`)**:
   - **Scope**: Read-only, edge-cached, fast CORS-enabled delivery of published content.
   - **Authentication**: `X-API-Key: <READ_ONLY_DELIVERY_KEY>` header.
   - **Use Cases**: Public blog frontends, marketing websites, documentation hubs, mobile reader apps, RSS/sitemap scrapers.

2. **Content Management REST API (`/api/v1/posts/*`)**:
   - **Scope**: Authenticated CRUD for programmatic drafting, updating, scheduling, and publishing.
   - **Authentication**: Cookie session or Workspace Bearer token.
   - **Use Cases**: AI writing pipelines, CMS migrations, multi-platform syndication, automated publishing bots.

---

## 2. API Reference & Contracts

Base URL: `https://aiblogr.app`

### A. Delivery Endpoints (Read-Only)

| Endpoint | Method | Description | Query Parameters |
| :--- | :---: | :--- | :--- |
| `/api/v1/public/posts` | `GET` | List published posts | `limit` (default: 20, max: 100), `cursor`, `category` (slug), `tag` (slug), `q` (search query), `featured` (`true`/`false`) |
| `/api/v1/public/posts/:slug` | `GET` | Get single post by slug | None |
| `/api/v1/public/categories` | `GET` | List all active categories | None |
| `/api/v1/public/tags` | `GET` | List all active tags | None |
| `/api/v1/public/sitemap` | `GET` | Get list of URLs & lastmod for sitemaps | None |
| `/api/v1/public/subscribe` | `POST` | Subscribe email to newsletter | Body: `{ "email": "user@example.com" }` |

#### Authentication Header
```http
X-API-Key: aiblog_live_xxxxxxxxxxxxxxxxxxxxxxxx
```

#### Example Response: `GET /api/v1/public/posts/:slug`
```json
{
  "success": true,
  "data": {
    "post": {
      "id": "post_12345",
      "title": "Scaling Next.js Applications with Edge Rendering",
      "slug": "scaling-nextjs-applications",
      "excerpt": "A deep dive into edge caching, ISR, and dynamic streaming in Next.js.",
      "content": "<h1>Scaling Next.js Applications</h1><p>Here is how...</p>",
      "contentFormat": "html",
      "readingTimeMinutes": 6,
      "featuredImageUrl": "https://aiblogr.app/uploads/hero.webp",
      "featuredImageAlt": "Next.js Edge Architecture Diagram",
      "publishedAt": "2026-10-01T12:00:00.000Z",
      "updatedAt": "2026-10-05T09:30:00.000Z",
      "author": {
        "name": "Jane Doe",
        "avatarUrl": "https://aiblogr.app/avatars/jane.webp",
        "bio": "Principal Infrastructure Engineer"
      },
      "categories": [
        { "id": "cat_1", "name": "Engineering", "slug": "engineering" }
      ],
      "tags": [
        { "id": "tag_1", "name": "Next.js", "slug": "nextjs" },
        { "id": "tag_2", "name": "Performance", "slug": "performance" }
      ],
      "seo": {
        "metaTitle": "Scaling Next.js Applications with Edge Rendering",
        "metaDescription": "Learn how to optimize and scale Next.js with edge compute and ISR.",
        "focusKeyword": "nextjs scaling",
        "canonical": "https://yoursite.com/blog/scaling-nextjs-applications",
        "openGraph": {
          "title": "Scaling Next.js Applications with Edge Rendering",
          "description": "Learn how to optimize and scale Next.js with edge compute.",
          "url": "https://yoursite.com/blog/scaling-nextjs-applications",
          "images": [{ "url": "https://aiblogr.app/uploads/hero.webp" }]
        },
        "twitter": {
          "card": "summary_large_image",
          "title": "Scaling Next.js Applications with Edge Rendering",
          "description": "Learn how to optimize and scale Next.js.",
          "image": "https://aiblogr.app/uploads/hero.webp"
        },
        "jsonLd": {
          "@context": "https://schema.org",
          "@type": "BlogPosting",
          "headline": "Scaling Next.js Applications with Edge Rendering",
          "description": "A deep dive into edge caching, ISR, and dynamic streaming in Next.js.",
          "datePublished": "2026-10-01T12:00:00.000Z",
          "dateModified": "2026-10-05T09:30:00.000Z",
          "image": "https://aiblogr.app/uploads/hero.webp",
          "author": {
            "@type": "Person",
            "name": "Jane Doe"
          }
        }
      }
    }
  }
}
```

---

## 3. TypeScript SDK Client (Zero-Dependency)

When writing code in TypeScript or JavaScript apps, create an `aiblogs.ts` client helper:

```typescript
// lib/aiblogs.ts

export interface AiBlogPost {
  id: string;
  title: string;
  slug: string;
  excerpt?: string;
  content: string;
  contentFormat: 'html' | 'markdown';
  readingTimeMinutes?: number;
  featuredImageUrl?: string;
  featuredImageAlt?: string;
  publishedAt: string;
  updatedAt: string;
  author?: {
    name: string;
    avatarUrl?: string;
    bio?: string;
  };
  categories: Array<{ id: string; name: string; slug: string }>;
  tags: Array<{ id: string; name: string; slug: string }>;
  seo: {
    metaTitle: string;
    metaDescription: string;
    focusKeyword?: string;
    canonical?: string;
    openGraph?: {
      title?: string;
      description?: string;
      url?: string;
      images?: Array<{ url: string; width?: number; height?: number; alt?: string }>;
    };
    twitter?: {
      card?: string;
      title?: string;
      description?: string;
      image?: string;
    };
    jsonLd?: Record<string, any>;
  };
}

export interface ListPostsParams {
  limit?: number;
  cursor?: string;
  category?: string;
  tag?: string;
  q?: string;
  featured?: boolean;
}

export class AiBlogsClient {
  private baseUrl: string;
  private apiKey: string;

  constructor(options: { baseUrl?: string; apiKey: string }) {
    this.baseUrl = (options.baseUrl || 'https://aiblogr.app').replace(/\/$/, '');
    this.apiKey = options.apiKey;
  }

  private async fetchApi<T>(path: string, options: RequestInit = {}): Promise<T> {
    const url = `${this.baseUrl}/api/v1/public${path}`;
    const res = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': this.apiKey,
        ...(options.headers || {}),
      },
    });

    if (!res.ok) {
      throw new Error(`AiBlogs API Error [${res.status}]: ${res.statusText}`);
    }

    const json = await res.json();
    return json.data;
  }

  async getPosts(params: ListPostsParams = {}) {
    const searchParams = new URLSearchParams();
    if (params.limit) searchParams.set('limit', params.limit.toString());
    if (params.cursor) searchParams.set('cursor', params.cursor);
    if (params.category) searchParams.set('category', params.category);
    if (params.tag) searchParams.set('tag', params.tag);
    if (params.q) searchParams.set('q', params.q);
    if (params.featured !== undefined) searchParams.set('featured', String(params.featured));

    const qs = searchParams.toString() ? `?${searchParams.toString()}` : '';
    return this.fetchApi<{ posts: AiBlogPost[]; nextCursor?: string }>(`/posts${qs}`);
  }

  async getPostBySlug(slug: string): Promise<AiBlogPost | null> {
    try {
      const data = await this.fetchApi<{ post: AiBlogPost }>(`/posts/${encodeURIComponent(slug)}`);
      return data.post;
    } catch {
      return null;
    }
  }

  async getCategories() {
    return this.fetchApi<{ categories: Array<{ id: string; name: string; slug: string; postCount: number }> }>('/categories');
  }

  async getTags() {
    return this.fetchApi<{ tags: Array<{ id: string; name: string; slug: string; postCount: number }> }>('/tags');
  }

  async getSitemap() {
    return this.fetchApi<{ urls: Array<{ url: string; lastmod: string; changefreq: string; priority: number }> }>('/sitemap');
  }

  async subscribe(email: string) {
    return this.fetchApi<{ success: boolean; message: string }>('/subscribe', {
      method: 'POST',
      body: JSON.stringify({ email }),
    });
  }
}

// Default singleton instance using environment variables
export const blogClient = new AiBlogsClient({
  baseUrl: process.env.AIBLOGS_BASE_URL || process.env.NEXT_PUBLIC_AIBLOGS_BASE_URL,
  apiKey: process.env.AIBLOGS_API_KEY || '',
});
```

---

## 4. Integration Recipes by Framework

### A. Next.js 14 / 15 (App Router with ISR & SEO)

#### 1. Blog Index Page (`app/blog/page.tsx`)
```tsx
import Link from 'next/link';
import Image from 'next/image';
import { blogClient } from '@/lib/aiblogs';

export const revalidate = 3600; // Edge cached, revalidated hourly (ISR)

export const metadata = {
  title: 'Blog & Engineering Insights',
  description: 'Latest articles, tutorials, and updates.',
};

export default async function BlogIndexPage() {
  const { posts } = await blogClient.getPosts({ limit: 12 });

  return (
    <main className="max-w-6xl mx-auto px-4 py-12">
      <h1 className="text-4xl font-extrabold tracking-tight mb-8">Latest Articles</h1>
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
        {posts.map((post) => (
          <article key={post.id} className="group border rounded-2xl overflow-hidden hover:shadow-lg transition">
            {post.featuredImageUrl && (
              <div className="relative aspect-video w-full overflow-hidden bg-muted">
                <Image
                  src={post.featuredImageUrl}
                  alt={post.featuredImageAlt || post.title}
                  fill
                  className="object-cover group-hover:scale-105 transition duration-300"
                />
              </div>
            )}
            <div className="p-6">
              <div className="flex items-center gap-2 text-xs text-muted-foreground mb-3">
                {post.categories[0] && (
                  <span className="font-semibold text-primary">{post.categories[0].name}</span>
                )}
                <span>•</span>
                <time dateTime={post.publishedAt}>{new Date(post.publishedAt).toLocaleDateString()}</time>
                {post.readingTimeMinutes && <span>• {post.readingTimeMinutes} min read</span>}
              </div>
              <h2 className="text-xl font-bold mb-2 group-hover:text-primary transition">
                <Link href={`/blog/${post.slug}`}>{post.title}</Link>
              </h2>
              <p className="text-muted-foreground text-sm line-clamp-3 mb-4">{post.excerpt}</p>
            </div>
          </article>
        ))}
      </div>
    </main>
  );
}
```

#### 2. Single Article Page with Full SEO & JSON-LD (`app/blog/[slug]/page.tsx`)
```tsx
import { notFound } from 'next/navigation';
import { Metadata } from 'next';
import Image from 'next/image';
import { blogClient } from '@/lib/aiblogs';

type Props = { params: { slug: string } };

export const revalidate = 3600;

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const post = await blogClient.getPostBySlug(params.slug);
  if (!post) return {};

  return {
    title: post.seo.metaTitle || post.title,
    description: post.seo.metaDescription || post.excerpt,
    alternates: { canonical: post.seo.canonical },
    openGraph: {
      title: post.seo.openGraph?.title || post.title,
      description: post.seo.openGraph?.description || post.excerpt,
      images: post.seo.openGraph?.images || (post.featuredImageUrl ? [{ url: post.featuredImageUrl }] : []),
    },
    twitter: {
      card: 'summary_large_image',
      title: post.seo.twitter?.title || post.title,
      description: post.seo.twitter?.description || post.excerpt,
      images: post.seo.twitter?.image ? [post.seo.twitter.image] : [],
    },
  };
}

export default async function BlogPostPage({ params }: Props) {
  const post = await blogClient.getPostBySlug(params.slug);
  if (!post) notFound();

  return (
    <article className="max-w-3xl mx-auto px-4 py-12">
      {/* Schema.org JSON-LD structured data for Google & AI search engines */}
      {post.seo.jsonLd && (
        <script
          type="application/ld+json"
          dangerouslySetInnerHTML={{ __html: JSON.stringify(post.seo.jsonLd) }}
        />
      )}

      <header className="mb-8">
        <div className="flex items-center gap-2 text-sm text-muted-foreground mb-4">
          <time dateTime={post.publishedAt}>{new Date(post.publishedAt).toLocaleDateString()}</time>
          {post.readingTimeMinutes && <span>• {post.readingTimeMinutes} min read</span>}
        </div>
        <h1 className="text-4xl sm:text-5xl font-extrabold tracking-tight leading-tight mb-6">
          {post.title}
        </h1>
        {post.author && (
          <div className="flex items-center gap-3">
            {post.author.avatarUrl && (
              <img src={post.author.avatarUrl} alt={post.author.name} className="w-10 h-10 rounded-full" />
            )}
            <div>
              <p className="font-medium text-sm">{post.author.name}</p>
              {post.author.bio && <p className="text-xs text-muted-foreground">{post.author.bio}</p>}
            </div>
          </div>
        )}
      </header>

      {post.featuredImageUrl && (
        <div className="relative aspect-video w-full rounded-2xl overflow-hidden mb-10 shadow-sm">
          <Image
            src={post.featuredImageUrl}
            alt={post.featuredImageAlt || post.title}
            fill
            priority
            className="object-cover"
          />
        </div>
      )}

      {/* Render HTML Content directly or Markdown */}
      <div
        className="prose prose-lg dark:prose-invert max-w-none"
        dangerouslySetInnerHTML={{ __html: post.content }}
      />
    </article>
  );
}
```

---

### B. Astro SSG / SSR Integration

```astro
---
// src/pages/blog/[slug].astro
import { blogClient } from '../../lib/aiblogs';

export async function getStaticPaths() {
  const { posts } = await blogClient.getPosts({ limit: 100 });
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

const { post } = Astro.props;
---

<html lang="en">
  <head>
    <title>{post.seo.metaTitle || post.title}</title>
    <meta name="description" content={post.seo.metaDescription || post.excerpt} />
    {post.seo.canonical && <link rel="canonical" href={post.seo.canonical} />}
    {post.seo.jsonLd && (
      <script type="application/ld+json" set:html={JSON.stringify(post.seo.jsonLd)} />
    )}
  </head>
  <body>
    <main class="container">
      <h1>{post.title}</h1>
      <div class="content" set:html={post.content} />
    </main>
  </body>
</html>
```

---

### C. React (Vite / Single Page App / Hook)

```typescript
// hooks/useBlog.ts
import { useState, useEffect } from 'react';
import { blogClient, AiBlogPost } from '@/lib/aiblogs';

export function usePost(slug: string) {
  const [post, setPost] = useState<AiBlogPost | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    setLoading(true);
    blogClient
      .getPostBySlug(slug)
      .then((data) => {
        setPost(data);
        setLoading(false);
      })
      .catch((err) => {
        setError(err.message);
        setLoading(false);
      });
  }, [slug]);

  return { post, loading, error };
}
```

---

### D. Python Integration (FastAPI, Django, or Automation Scripts)

```python
import os
import requests
from typing import Optional, Dict, Any, List

class AiBlogsClient:
    def __init__(self, api_key: str, base_url: str = "https://aiblogr.app"):
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.headers = {
            "X-API-Key": self.api_key,
            "Content-Type": "application/json"
        }

    def get_posts(self, limit: int = 20, category: Optional[str] = None) -> List[Dict[str, Any]]:
        params = {"limit": limit}
        if category:
            params["category"] = category
        res = requests.get(f"{self.base_url}/api/v1/public/posts", headers=self.headers, params=params)
        res.raise_for_status()
        return res.json().get("data", {}).get("posts", [])

    def get_post_by_slug(self, slug: str) -> Optional[Dict[str, Any]]:
        res = requests.get(f"{self.base_url}/api/v1/public/posts/{slug}", headers=self.headers)
        if res.status_code == 404:
            return None
        res.raise_for_status()
        return res.json().get("data", {}).get("post")

# Usage
client = AiBlogsClient(api_key=os.environ["AIBLOGS_API_KEY"])
posts = client.get_posts(limit=5)
for p in posts:
    print(p["title"], "->", p["slug"])
```

---

## 5. Automated AI Content Publishing (Management API)

LLM agents generating or scheduling content programmatically can call the Post Management API:

### Create a Draft Post
```bash
curl -X POST https://aiblogr.app/api/v1/posts \
  -H "Authorization: Bearer <WORKSPACE_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "workspaceId": "YOUR_WORKSPACE_UUID",
    "title": "Autonomous AI Coding in 2026",
    "slug": "autonomous-ai-coding-2026",
    "body": {
      "format": "markdown",
      "content": "## Introduction\n\nAI agents are transforming software engineering..."
    },
    "excerpt": "How agentic AI workflows accelerate modern development cycles.",
    "status": "draft",
    "seo": {
      "metaTitle": "Autonomous AI Coding in 2026 | Tech Guide",
      "metaDescription": "Explore autonomous AI agents and coding tools in 2026.",
      "focusKeyword": "autonomous ai coding"
    }
  }'
```

### Publish Post Immediately
```bash
curl -X POST https://aiblogr.app/api/v1/posts/:postId/publish \
  -H "Authorization: Bearer <WORKSPACE_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "publish",
    "notifySubscribers": true
  }'
```

---

## 6. Prompt Templates for AI Assistants

When instructing an LLM or AI Coding Assistant to implement a blog integration, provide this prompt:

```text
You are integrating the AI Blogs Headless CMS into this project.
Follow the guidelines below:
1. Use the read-only Delivery API (`/api/v1/public/*`) with header `X-API-Key: process.env.AIBLOGS_API_KEY`.
2. Fetch blog posts via `GET /api/v1/public/posts` and single articles via `GET /api/v1/public/posts/:slug`.
3. Support full SEO metadata: render canonical URLs, Open Graph tags, Twitter cards, and inject `post.seo.jsonLd` into the <head> as Schema.org JSON-LD.
4. Implement edge caching or Incremental Static Regeneration (revalidate: 3600).
5. Render clean typography with responsive images and category tags.
```

---

## 7. Troubleshooting & Best Practices

- **Invalid API Key (401/403)**: Ensure `X-API-Key` is passed in headers, and that the key is generated from **Settings → API keys** in the workspace.
- **Drafts not showing**: The public delivery API only returns posts with status `published`. Check the post status in the workspace dashboard.
- **CORS Issues**: `/api/v1/public/*` routes allow cross-origin requests (`Access-Control-Allow-Origin: *`) from any domain.
- **Subdirectory Hosting**: If hosting under `yoursite.com/blog`, ensure your reverse proxy passes `X-Blog-Prefix: /blog` and `X-Blog-Host: yoursite.com`.
