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=htmlforbodyHtml. 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
- Render posts on your domain at real URLs (
/blog/{slug}). - Emit
<title>fromtitleand meta description frommetaDescription. - Add every post to
/sitemap.xmland submit it in Search Console. - Serve the RSS feed URL to readers and aggregators.
- Link new posts from your homepage or footer so crawlers discover them.