Content Platform Guide
Overview
The SpiderIQ Content Platform is a headless, multi-tenant CMS built for developers and AI agents. Every client gets isolated content (posts, pages, authors, categories, tags) accessible via two API layers:
- Public API (
/api/v1/content/*) -- Read-only, no authentication. Designed for external frontends. - Dashboard API (
/api/v1/dashboard/content/*) -- Full CRUD, requires Bearer authentication. Designed for content management.
Content is stored per-tenant in PostgreSQL. The public API resolves the tenant from the X-Content-Domain header, while the Dashboard API resolves it from the Bearer token.
For MCP tools (146+), CLI commands, Claude Code setup, and deploying sites from code, see the Site Builder docs. The Site Builder section has a complete AGENTS.md integration guide and a drop-in CLAUDE.md for your project.
Multi-tenant isolation -- Clients can never read or modify another client's content. Tenant isolation is enforced at the database query level.
Authentication
All Dashboard (CRUD) endpoints require a Bearer token in the format:
Authorization: Bearer <client_id>:<api_key>:<api_secret>
You receive these credentials when your SpiderIQ account is created. The same token is used across all SpiderIQ APIs (jobs, content, gate, etc.).
Public (read-only) endpoints require no authentication. Instead, the client is resolved from the X-Content-Domain header, which maps a domain name to a client account.
Architecture
┌─────────────────────────┐
│ External Frontend │
│ (WeWeb, Next.js, etc.) │
└────────────┬────────────┘
│
X-Content-Domain: your-domain.com
│
┌────────────▼────────────┐
│ Public Content API │
│ /api/v1/content/* │
│ (read-only, no auth) │
└────────────┬────────────┘
│
┌──────────────┐ ┌────────────▼────────────┐
│ Dashboard │ │ │
│ (CRUD API) ├───►│ PostgreSQL (tenant) │
│ Bearer auth │ │ │
└──────────────┘ └─────────────────────────┘
│
│ Authorization: Bearer <client_id>:<api_key>:<api_secret>
│
┌──────▼──────────────────┐
│ Dashboard Content API │
│ /api/v1/dashboard/ │
│ content/* │
│ (full CRUD) │
└──────────────────────────┘
Quick Start
This walkthrough creates an author, a category, a tag, a post, publishes it, and then reads it via the public API.
Step 1: Create an Author
- cURL
- Python
- JavaScript
curl -X POST "https://spideriq.ai/api/v1/dashboard/content/authors" \
-H "Authorization: Bearer $CLIENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"full_name": "Sarah Chen",
"bio": "Technical writer covering APIs and developer tools.",
"role": "author",
"agent_type": "human"
}'
import requests
API = "https://spideriq.ai/api/v1/dashboard/content"
HEADERS = {"Authorization": f"Bearer {CLIENT_TOKEN}"}
author = requests.post(f"{API}/authors", headers=HEADERS, json={
"full_name": "Sarah Chen",
"bio": "Technical writer covering APIs and developer tools.",
"role": "author",
"agent_type": "human",
}).json()
author_id = author["id"]
print(f"Author: {author['full_name']} ({author_id})")
const API = "https://spideriq.ai/api/v1/dashboard/content";
const headers = {
"Authorization": `Bearer ${CLIENT_TOKEN}`,
"Content-Type": "application/json",
};
const author = await fetch(`${API}/authors`, {
method: "POST",
headers,
body: JSON.stringify({
full_name: "Sarah Chen",
bio: "Technical writer covering APIs and developer tools.",
role: "author",
agent_type: "human",
}),
}).then(r => r.json());
console.log(`Author: ${author.full_name} (${author.id})`);
Step 2: Create a Category
- cURL
- Python
- JavaScript
curl -X POST "https://spideriq.ai/api/v1/dashboard/content/categories" \
-H "Authorization: Bearer $CLIENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Tutorials",
"description": "Step-by-step guides"
}'
category = requests.post(f"{API}/categories", headers=HEADERS, json={
"name": "Tutorials",
"description": "Step-by-step guides",
}).json()
category_id = category["id"]
print(f"Category: {category['name']} ({category_id})")
const category = await fetch(`${API}/categories`, {
method: "POST",
headers,
body: JSON.stringify({
name: "Tutorials",
description: "Step-by-step guides",
}),
}).then(r => r.json());
console.log(`Category: ${category.name} (${category.id})`);
Step 3: Create a Tag
- cURL
- Python
- JavaScript
curl -X POST "https://spideriq.ai/api/v1/dashboard/content/tags" \
-H "Authorization: Bearer $CLIENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "getting-started" }'
tag = requests.post(f"{API}/tags", headers=HEADERS, json={
"name": "getting-started",
}).json()
print(f"Tag: {tag['name']} ({tag['id']})")
const tag = await fetch(`${API}/tags`, {
method: "POST",
headers,
body: JSON.stringify({ name: "getting-started" }),
}).then(r => r.json());
console.log(`Tag: ${tag.name} (${tag.id})`);
Step 4: Create a Post
- cURL
- Python
- JavaScript
curl -X POST "https://spideriq.ai/api/v1/dashboard/content/posts" \
-H "Authorization: Bearer $CLIENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "hello-world",
"title": "Hello World",
"body": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Welcome to our blog!" }]
}
]
},
"excerpt": "Our first post.",
"author_id": "<AUTHOR_ID>",
"category_ids": ["<CATEGORY_ID>"],
"tags": ["getting-started"]
}'
post = requests.post(f"{API}/posts", headers=HEADERS, json={
"slug": "hello-world",
"title": "Hello World",
"body": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [{"type": "text", "text": "Welcome to our blog!"}],
}
],
},
"excerpt": "Our first post.",
"author_id": author_id,
"category_ids": [category_id],
"tags": ["getting-started"],
}).json()
post_id = post["id"]
print(f"Post: {post['title']} (status: {post['status']})")
const post = await fetch(`${API}/posts`, {
method: "POST",
headers,
body: JSON.stringify({
slug: "hello-world",
title: "Hello World",
body: {
type: "doc",
content: [
{
type: "paragraph",
content: [{ type: "text", text: "Welcome to our blog!" }],
},
],
},
excerpt: "Our first post.",
author_id: author.id,
category_ids: [category.id],
tags: ["getting-started"],
}),
}).then(r => r.json());
console.log(`Post: ${post.title} (status: ${post.status})`);
Step 5: Publish the Post
- cURL
- Python
- JavaScript
curl -X POST "https://spideriq.ai/api/v1/dashboard/content/posts/<POST_ID>/publish" \
-H "Authorization: Bearer $CLIENT_TOKEN"
published = requests.post(
f"{API}/posts/{post_id}/publish", headers=HEADERS
).json()
print(f"Status: {published['status']}") # "published"
print(f"Published at: {published['published_at']}")
const published = await fetch(`${API}/posts/${post.id}/publish`, {
method: "POST",
headers,
}).then(r => r.json());
console.log(`Status: ${published.status}`); // "published"
Step 6: Read via Public API
- cURL
- Python
- JavaScript
curl -s "https://spideriq.ai/api/v1/content/posts/hello-world" \
-H "X-Content-Domain: your-domain.com"
public_post = requests.get(
"https://spideriq.ai/api/v1/content/posts/hello-world",
headers={"X-Content-Domain": "your-domain.com"},
).json()
print(f"Title: {public_post['title']}")
print(f"Author: {public_post['author']['full_name']}")
print(f"Tags: {public_post['tags']}")
const publicPost = await fetch(
"https://spideriq.ai/api/v1/content/posts/hello-world",
{ headers: { "X-Content-Domain": "your-domain.com" } }
).then(r => r.json());
console.log(`Title: ${publicPost.title}`);
console.log(`Author: ${publicPost.author.full_name}`);
Content Types
SpiderIQ's Content Platform supports three content types:
Posts (Blog)
Blog posts use Tiptap JSON for rich text content. Posts support:
- Authors -- human or AI agent authors
- Categories -- hierarchical grouping (Tutorials > API Guides)
- Tags -- flat labels for cross-cutting topics
- Related posts -- manual or auto-linked related content
- Featured flag -- mark posts for homepage prominence
- SEO fields -- custom title, description, OG image
- View tracking -- automatic view count on each public read
- Full-text search -- search across title, body, excerpt, tags
Pages (Marketing)
Pages use a block-based content model for structured layouts. Block types include:
| Block Type | Use Case |
|---|---|
hero | Landing page hero with heading, subheading, CTA |
text | Rich text paragraphs |
cta | Call-to-action buttons and sections |
faq | Frequently asked questions |
pricing | Pricing plan grids |
features | Feature lists or grids |
image | Image with optional caption |
video | Embedded video |
testimonials | Customer testimonial carousel |
Pages are ideal for About, Pricing, Contact, and Landing pages.
Docs (Documentation)
Documentation pages are organized in a tree structure with parent-child relationships. Each doc has:
slug-- URL path segmenttitle-- Page titlebody-- Tiptap JSON contentparent_id-- For nesting (Docs > Getting Started > Installation)sort_order-- Display order within parent
Use the Get Doc and Doc Tree endpoints to read documentation.
Blog System
Authors
Authors represent the people (or AI agents) who create content. Each post has one author.
| Field | Description |
|---|---|
full_name | Display name |
slug | URL-friendly identifier (auto-generated) |
role | author, editor, contributor, or administrator |
agent_type | human or ai -- lets readers know if content was AI-generated |
avatar_url | Profile image |
bio | Short biography |
Categories (Hierarchical)
Categories provide a hierarchical taxonomy. Use parent_id to create nested trees:
Tutorials
├── API Guides
├── Scraping Guides
└── Automation
Product Updates
├── Features
└── Bug Fixes
Tags (Flat)
Tags are flat labels that cut across categories. A post about "Scraping Google Maps" might be in the "Tutorials" category with tags google-maps, scraping, seo.
Post Lifecycle
Posts move through a defined status lifecycle:
draft → pending_review → published → archived
| Status | Visible on public API? | Description |
|---|---|---|
draft | No | Initial state, work in progress |
pending_review | No | Ready for editorial review |
published | Yes | Live and publicly accessible |
archived | No | Removed from public, preserved in DB |
Use Change Post Status to transition between any states, or the shortcut endpoints Publish and Unpublish.
Featured Posts
Mark posts as is_featured: true to highlight them. Query featured posts via the Featured Posts endpoint.
Full-Text Search
Search across post titles, body content, excerpts, and tags using the Search Posts endpoint:
curl -s "https://spideriq.ai/api/v1/content/posts/search?q=google+maps&limit=10" \
-H "X-Content-Domain: your-domain.com"
Using with External Frontends
The public Content API is designed for headless use with any frontend framework. Set the X-Content-Domain header to your site's domain so SpiderIQ resolves the correct tenant.
How It Works
- Register your domain with SpiderIQ (mapped to your client account)
- Your frontend sends requests to
https://spideriq.ai/api/v1/content/* - Include
X-Content-Domain: your-domain.comin every request - SpiderIQ returns only your client's published content
WeWeb / Webflow / Bubble
For no-code tools, configure an API data source:
- Base URL:
https://spideriq.ai/api/v1/content - Headers:
X-Content-Domain: your-domain.com - Endpoints:
/posts,/posts/{slug},/pages/{slug},/categories,/tags,/authors
Next.js Example
// lib/content.js
const CONTENT_API = "https://spideriq.ai/api/v1/content";
const DOMAIN = "your-domain.com";
export async function getPosts(page = 1, limit = 10) {
const resp = await fetch(
`${CONTENT_API}/posts?page=${page}&limit=${limit}`,
{ headers: { "X-Content-Domain": DOMAIN } }
);
return resp.json();
}
export async function getPost(slug) {
const resp = await fetch(
`${CONTENT_API}/posts/${slug}`,
{ headers: { "X-Content-Domain": DOMAIN } }
);
return resp.json();
}
export async function getCategories() {
const resp = await fetch(
`${CONTENT_API}/categories`,
{ headers: { "X-Content-Domain": DOMAIN } }
);
return resp.json();
}
// app/blog/page.js (Next.js App Router)
import { getPosts, getCategories } from "@/lib/content";
export default async function BlogPage() {
const [postsData, categoriesData] = await Promise.all([
getPosts(),
getCategories(),
]);
return (
<div>
<h1>Blog</h1>
<nav>
{categoriesData.categories.map(cat => (
<a key={cat.id} href={`/blog/category/${cat.slug}`}>
{cat.name}
</a>
))}
</nav>
<div>
{postsData.posts.map(post => (
<article key={post.id}>
<h2><a href={`/blog/${post.slug}`}>{post.title}</a></h2>
<p>{post.excerpt}</p>
<span>By {post.author.full_name}</span>
</article>
))}
</div>
</div>
);
}
Static Site Generation
The Content API works well with SSG frameworks. Fetch content at build time:
// Astro, Eleventy, Hugo, etc.
// Fetch all post slugs for static path generation
const resp = await fetch(
"https://spideriq.ai/api/v1/content/posts?limit=1000",
{ headers: { "X-Content-Domain": "your-domain.com" } }
);
const data = await resp.json();
const slugs = data.posts.map(p => p.slug);
AI Agent Integration
AI agents can manage content entirely through the API, with no human interaction required.
MCP Tools
If your AI agent supports the Model Context Protocol, install the SpiderIQ MCP server:
{
"mcpServers": {
"spideriq": {
"command": "npx",
"args": ["@spideriq/mcp"],
"env": { "SPIDERIQ_FORMAT": "yaml" }
}
}
}
Available content MCP tools:
| Tool | Description |
|---|---|
content_list_pages | List published pages |
content_create_page | Create a new page |
content_get_page | Get page by slug |
content_update_page | Update a page |
content_delete_page | Delete a page |
content_publish_page | Publish a page |
content_create_post | Create a blog post |
content_publish_post | Publish a post |
CLI Commands
# Create a post via CLI
npx spideriq content create-post \
--slug "ai-generated-post" \
--title "AI Generated Content" \
--body '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Written by AI."}]}]}' \
--tags "ai,automation"
# Publish it
npx spideriq content publish-post --id <post-id>
# List all posts
npx spideriq content list-posts --format yaml
Agent Workflow Example
A typical AI agent content workflow:
import requests
API = "https://spideriq.ai/api/v1/dashboard/content"
HEADERS = {"Authorization": f"Bearer {CLIENT_TOKEN}"}
# 1. Ensure author exists
authors = requests.get(f"{API}/authors", headers=HEADERS).json()
# (or create one if needed)
# 2. Generate content with your LLM
title = "10 Lead Generation Tips for 2026"
body_content = generate_blog_post(title) # your LLM call
# 3. Create the post
post = requests.post(f"{API}/posts", headers=HEADERS, json={
"slug": "lead-generation-tips-2026",
"title": title,
"body": body_content,
"excerpt": "Proven lead generation strategies for the AI era.",
"tags": ["lead-generation", "tips", "2026"],
"author_id": authors[0]["id"],
}).json()
# 4. Review or auto-publish
# Option A: Set to pending_review for human approval
requests.post(f"{API}/posts/{post['id']}/status", headers=HEADERS,
json={"status": "pending_review"})
# Option B: Auto-publish immediately
requests.post(f"{API}/posts/{post['id']}/publish", headers=HEADERS)
API Reference
Public Endpoints (Read-Only, No Auth)
| Endpoint | Description |
|---|---|
| List Posts | List published posts with pagination |
| Get Post | Get a post by slug |
| Search Posts | Full-text search |
| Featured Posts | List featured posts |
| List Authors | List active authors |
| Get Author | Get author by slug |
| List Categories | List categories with hierarchy |
| List Tags | List tags with post counts |
| List Pages | List published pages |
| Get Page | Get a page by slug |
| Doc Tree | Get documentation tree |
| Get Doc | Get a doc by slug |
| Navigation | Get navigation menus |
| Settings | Get site-wide settings |
| Sitemap | Auto-generated sitemap |
Dashboard Endpoints (CRUD, Bearer Auth)
Posts:
| Endpoint | Description |
|---|---|
| Create Post | Create a new post |
| Update Post | Update a post |
| Delete Post | Delete a post |
| Publish Post | Publish a post |
| Unpublish Post | Revert to draft |
| Change Status | Set any lifecycle status |
Authors:
| Endpoint | Description |
|---|---|
| Create Author | Create an author |
| Update Author | Update an author |
| Delete Author | Deactivate an author |
Tags:
| Endpoint | Description |
|---|---|
| Create Tag | Create a tag |
| Update Tag | Update a tag |
| Delete Tag | Delete a tag |
Categories:
| Endpoint | Description |
|---|---|
| Create Category | Create a category |
| Update Category | Update a category |
| Delete Category | Delete a category |
Pages:
| Endpoint | Description |
|---|---|
| Create Page | Create a page |
| Update Page | Update a page |
| Delete Page | Delete a page |
| Publish Page | Publish a page |
| Unpublish Page | Revert to draft |