Host Your Blog on SEO Beast

A fast, public, no-auth content API — power your blog on any stack without running WordPress.

Version 1.0REST APIllms.txt

Using Claude Code, Cursor, or another AI coding agent? Copy the prompt above, paste it into your agent, add your website slug — it has everything needed to wire up your blog.

Why serve your blog from SEO Beast?

  • No CMS to run — no WordPress hosting, updates, or plugins
  • Posts, categories, tags, and authors update the moment content publishes
  • Works with Next.js, Astro, Nuxt, SvelteKit, or a plain fetch call

Base URL

https://api.seobeast.io/v1/public

Authentication

No authentication required. All endpoints are publicly accessible for published content only.

Response Format

Success Response

{
  "success": true,
  "data": { /* response data */ },
  "meta": {
    "websiteSlug": "example-blog",
    "requestedAt": "2024-12-15T10:00:00Z",
    "version": "v1"
  }
}

Error Response

{
  "success": false,
  "error": "Error message",
  "details": "Additional error details (if applicable)"
}

Endpoints

Posts

GET/{websiteSlug}/posts

Get a paginated list of published posts

Query Parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
limitintegerPosts per page, 1-50 (default: 10)
categorystringFilter by category slug
tagstringFilter by tag slug
post_typestringFilter by post type
searchstringSearch in title and content (min 2 chars)
sortstringSort by: published_at, title, view_count
orderstringSort order: asc, desc (default: desc)

Example

GET /v1/public/strong-curves/posts?limit=5&category=fitness
GET/{websiteSlug}/posts/{slug}

Get a single post by slug

Query Parameters

ParameterTypeDescription
include_contentbooleanInclude full content (default: true)
include_relatedbooleanInclude related posts (default: false)
related_limitintegerNumber of related posts, 1-10 (default: 3)

Example

GET /v1/public/strong-curves/posts/getting-started?include_related=true

Categories

GET/{websiteSlug}/categories

Get list of categories with hierarchical structure

Query Parameters

ParameterTypeDescription
include_countsbooleanInclude post counts (default: true)
parent_idstringFilter by parent category UUID
sortstringSort by: name, sort_order, post_count

Example

GET /v1/public/strong-curves/categories

Tags

GET/{websiteSlug}/tags

Get list of tags with usage statistics

Query Parameters

ParameterTypeDescription
limitintegerMax tags to return, 1-100 (default: 50)
min_usageintegerMinimum usage count (default: 0)
searchstringSearch tag names (min 1 char)
sortstringSort by: name, usage_count, created_at

Example

GET /v1/public/strong-curves/tags?limit=20&min_usage=5

RSS/Atom/JSON Feeds

GET/{websiteSlug}/feed

Get RSS, Atom, or JSON feed of published posts

Query Parameters

ParameterTypeDescription
formatstringFeed format: rss, atom, json (default: rss)
limitintegerMax posts in feed, 1-50 (default: 20)
categorystringFilter by category slug
tagstringFilter by tag slug

Examples

GET /v1/public/strong-curves/feedGET /v1/public/strong-curves/feed?format=atomGET /v1/public/strong-curves/feed?format=json

Comments

GET/{websiteSlug}/posts/{slug}/comments

Get comments for a specific post with user reaction data

Query Parameters

ParameterTypeDescription
visitorIdstringRequired. Unique visitor identifier for personalized data

Response Fields

  • like_count - Number of likes on the comment
  • user_reaction - Current user's reaction ("like", "dislike", or null)
  • replies - Nested array of reply comments

Example

GET /v1/public/strong-curves/posts/hip-dips-guide/comments?visitorId=visitor_123
POST/{websiteSlug}/posts/{slug}/comments

Create a new comment on a post

Request Body

{
  "content": "Great article! Very helpful.",
  "author_name": "John Doe",
  "author_email": "john@example.com",
  "visitorId": "visitor_123",
  "parent_id": null  // Optional: UUID for reply comments
}

Example

POST /v1/public/strong-curves/posts/hip-dips-guide/comments

Comment Reactions

POST/{websiteSlug}/posts/{slug}/comments/{commentId}/reactions

Add, update, or remove a reaction on a comment

Request Body

{
  "type": "like",           // "like" or "dislike"
  "visitorId": "visitor_123"
}

Behavior

  • First reaction: Creates new reaction
  • Same reaction: Removes reaction (toggle off)
  • Different reaction: Updates to new reaction type
  • Like count: Automatically updated in real-time

Response

{
  "success": true,
  "data": {
    "message": "Reaction updated successfully",
    "comment": { /* updated comment with new like_count */ },
    "reaction": "like",      // Current reaction or null if removed
    "action": "added"        // "added", "updated", or "removed"
  }
}

Example

POST /v1/public/strong-curves/posts/hip-dips-guide/comments/c2f98dec-b3ea-4c9f-a336-26538a2c0b11/reactions

Rate Limiting & Caching

Rate Limiting

Currently no rate limiting is enforced. This will be added in future versions with 1000 requests per hour for free tier.

Caching

  • Posts list: 5 minutes
  • Single post: 10 minutes
  • Categories: 30 minutes
  • Tags: 15 minutes
  • Feeds: 1 hour

Integration Examples

JavaScript/Fetch

// Fetch latest posts
const response = await fetch('/v1/public/strong-curves/posts?limit=5');
const data = await response.json();

if (data.success) {
  // Process posts
  // data.data.posts
  // data.data.pagination.totalPages
}

Next.js (App Router)

// app/blog/[slug]/page.tsx
export const revalidate = 600; // re-fetch every 10 minutes

export default async function PostPage({ params }) {
  const { slug } = await params;
  const res = await fetch(
    `https://api.seobeast.io/v1/public/your-site/posts/${slug}`
  );
  const { data: post } = await res.json();

  return <article>{/* render post.title, post.content */}</article>;
}

Astro

---
// src/pages/blog/[slug].astro
const { slug } = Astro.params;
const res = await fetch(
  `${import.meta.env.API_BASE}/v1/public/your-site/posts/${slug}`
);
const { data: post } = await res.json();
---
<article>
  <h1>{post.title}</h1>
  <Fragment set:html={post.contentHtml} />
</article>

Nuxt

<!-- pages/blog/[slug].vue -->
<script setup>
const route = useRoute();
const { data } = await useFetch(
  `${useRuntimeConfig().public.apiBase}/v1/public/your-site/posts/${route.params.slug}`
);
const post = computed(() => data.value?.data);
</script>

<template>
  <article><h1>{{ post.title }}</h1></article>
</template>

SvelteKit

// src/routes/blog/[slug]/+page.server.js
export async function load({ params, fetch }) {
  const res = await fetch(
    `https://api.seobeast.io/v1/public/your-site/posts/${params.slug}`
  );
  const { data: post } = await res.json();
  return { post };
}

React Hook

import { useState, useEffect } from 'react';

function usePosts(websiteSlug, params = {}) {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchPosts = async () => {
      const searchParams = new URLSearchParams(params);
      const response = await fetch(`/v1/public/${websiteSlug}/posts?${searchParams}`);
      const data = await response.json();
      
      if (data.success) {
        setPosts(data.data.posts);
      }
      setLoading(false);
    };
    
    fetchPosts();
  }, [websiteSlug, params]);
  
  return { posts, loading };
}

Webhooks: instant cache updates

The read API is public and needs no key. Webhooks are the optional other half: SEO Beast calls your site the moment a post is published, updated, or deleted, so your cache clears instantly instead of waiting for a revalidation window. This is the only part of the integration that uses a secret.

Setup

  1. In the dashboard, open Settings → Webhooks for your website.
  2. Set your receiver URL (e.g. https://yoursite.com/api/webhooks/seobeast) and click Generate secret.
  3. Copy the secret into your app’s environment (e.g. SEOBEAST_WEBHOOK_SECRET). Regenerating it immediately invalidates the old one.

Request signing

Every webhook POST carries an X-Webhook-Signature header: an HMAC-SHA256 of the raw request body, keyed with your secret. The body includes a timestamp field — verify the signature, then reject anything older than a few minutes to prevent replays.

Receiver (Next.js reference)

// app/api/webhooks/seobeast/route.ts
import crypto from "crypto";

export async function POST(request: Request) {
  const secret = process.env.SEOBEAST_WEBHOOK_SECRET!;
  const signature = request.headers.get("x-webhook-signature") ?? "";
  const rawBody = await request.text();

  // 1. Verify the HMAC signature (timing-safe)
  const expected = crypto.createHmac("sha256", secret)
    .update(rawBody).digest("hex");
  const ok = signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  if (!ok) return new Response("Invalid signature", { status: 401 });

  const payload = JSON.parse(rawBody);

  // 2. Replay protection: reject payloads older than 5 minutes.
  // payload.timestamp is inside the signed body, so it can't be forged.
  const sentAt = new Date(payload.timestamp).getTime();
  if (!Number.isFinite(sentAt) || Math.abs(Date.now() - sentAt) > 5 * 60_000) {
    return new Response("Stale webhook", { status: 401 });
  }

  // 3. Revalidate the affected content
  // e.g. revalidateTag("blog-posts"); revalidatePath(`/blog/${payload.data.slug}`)
  return new Response("ok");
}

For more information or support, visit the API info endpoint for machine-readable API details.