Docs

Content API

Read-only access to published posts so your site can render the blog on its own domain. Authenticate every request with a brand-scoped key from Settings → Delivery.

Also see the webhook reference.

Authentication

Authorization: Bearer sk_live_…

A key can only read that brand's posts. Keys are revocable and rate limited to 120 requests per minute. Responses carry Cache-Control: public, s-maxage=300, stale-while-revalidate=600 and CORS Access-Control-Allow-Origin: *.

Endpoints

Base URL: https://seogeoaeo.ai/api/public/v1

  • GET /posts — cursor-paginated published posts. Query: limit (1–50, default 20), cursor, group.
  • GET /posts/{slug} — one published post. Add ?format=html for bodyHtml. Drafts return 404.
  • GET /groups — categories with post counts.
  • GET /feed.xml — RSS of the latest 50 posts.
  • GET /openapi.json — machine-readable spec.

JSON fields are camelCase: metaDescription, publishedAt, bodyMarkdown, bodyHtml.

Next.js (App Router)

const API = "https://seogeoaeo.ai/api/public/v1";
const headers = { authorization: `Bearer ${process.env.CONTENT_API_KEY!}` };

export const revalidate = 300;

export default async function BlogPage() {
  const { posts } = await fetch(`${API}/posts`, { headers, next: { revalidate: 300 } })
    .then((r) => r.json());
  return (
    <main>
      <h1>Blog</h1>
      <ul>
        {posts.map((p) => (
          <li key={p.id}>
            <a href={`/blog/${p.slug}`}>{p.title}</a>
            <p>{p.excerpt}</p>
          </li>
        ))}
      </ul>
    </main>
  );
}
export const revalidate = 300;

export async function generateStaticParams() {
  const { posts } = await fetch(`${API}/posts`, { headers })
    .then((r) => r.json());
  return posts.map((p) => ({ slug: p.slug }));
}

export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const { post } = await fetch(`${API}/posts/${slug}?format=html`, {
    headers,
    next: { revalidate: 300 },
  }).then((r) => r.json());
  return (
    <article>
      <h1>{post.title}</h1>
      <time dateTime={post.publishedAt}>{post.publishedAt}</time>
      <div dangerouslySetInnerHTML={{ __html: post.bodyHtml }} />
    </article>
  );
}
// app/sitemap.ts
export default async function sitemap() {
  const { posts } = await fetch(`${API}/posts`, { headers }).then((r) => r.json());
  return posts.map((p) => ({
    url: `https://example.com/blog/${p.slug}`,
    lastModified: p.updatedAt,
  }));
}

Astro

---
const headers = { authorization: `Bearer ${import.meta.env.CONTENT_API_KEY}` };
const { posts } = await fetch("https://seogeoaeo.ai/api/public/v1/posts", { headers }).then((r) => r.json());
---
<ul>
  {posts.map((p) => (
    <li><a href={`/blog/${p.slug}/`}>{p.title}</a><p>{p.excerpt}</p></li>
  ))}
</ul>
---
export async function getStaticPaths() {
  const headers = { authorization: `Bearer ${import.meta.env.CONTENT_API_KEY}` };
  const { posts } = await fetch("https://seogeoaeo.ai/api/public/v1/posts", { headers }).then((r) => r.json());
  return posts.map((p) => ({ params: { slug: p.slug } }));
}

const { slug } = Astro.params;
const headers = { authorization: `Bearer ${import.meta.env.CONTENT_API_KEY}` };
const { post } = await fetch(
  `https://seogeoaeo.ai/api/public/v1/posts/${slug}?format=html`,
  { headers },
).then((r) => r.json());
---
<article set:html={post.bodyHtml} />

Nuxt

Same shape as Next.js. Use a server route or useFetch against the cached API. CORS allows browser reads. Generate routes from GET /posts in nitro.prerender.routes or a sitemap.ts equivalent.

const { data } = await useFetch("https://seogeoaeo.ai/api/public/v1/posts", {
  headers: { authorization: `Bearer ${useRuntimeConfig().contentApiKey}` },
});

SEO checklist for your blog pages

  1. Render posts on your domain at real URLs (/blog/{slug}).
  2. Emit <title> from title and meta description from metaDescription.
  3. Add every post to /sitemap.xml and submit it in Search Console.
  4. Serve the RSS feed URL to readers and aggregators.
  5. Link new posts from your homepage or footer so crawlers discover them.