Docs›REST API
REST API
BobBuilds generates blogs inside your workspace, stores them on your instance, and exposes them through a simple REST API so your website or CMS can pull them in and publish them downstream.
How the integration works
1. Generate inside BobBuilds
Your blog generator creates content and stores it in BobBuilds with statuses like generating, ready, and published.
2. Pull into the destination CMS
Your site, CMS, or automation layer calls BobBuilds API endpoints, reads the blog payload, and creates or updates the post in WordPress, Webflow, Sanity, Ghost, or a custom backend.
3. Or push to a webhook
If the customer prefers event-driven delivery, BobBuilds can POST the published blog payload to their webhook, API gateway, or CMS automation endpoint.
Recommended publish flow
The baseline pattern is: generate the blog in BobBuilds, mark it as published when approved, then either let the customer's CMS integration pull from /api/v1/blogs or call the delivery route to POST the payload into their own content system.
Base URL
https://your-bobbuilds-instance.com/api/v1Authentication
Pass your workspace API key as a Bearer token. Generate one from the dashboard and use it from your external site, CMS sync worker, cron job, or webhook consumer.
Authorization: Bearer bb_your_key_hereThese routes send Access-Control-Allow-Origin: *, so browser-based CMS integrations are also possible.
For webhook delivery, BobBuilds can also sign the outgoing payload with an HMAC SHA-256 signature in X-BobBuilds-Signature.
Blog lifecycle
| Status | Meaning |
|---|---|
| generating | Blog generation is in progress |
| ready | Content exists but has not been published yet |
| published | Visible through the external REST API and ready for CMS sync |
Endpoints
/api/v1/blogsList all published blogs for your workspace. Use this from a CMS sync job, build hook, or scheduled importer to fetch the latest content BobBuilds has generated and approved.
Parameters
pagequeryPage number starting at 1limitqueryResults per page, max 100, default 20Example
curl https://your-bobbuilds-instance.com/api/v1/blogs \
-H "Authorization: Bearer bb_your_key_here"Response
{
"blogs": [
{
"id": "blog_abc123",
"title": "How to Improve AI Visibility",
"slug": "how-to-improve-ai-visibility",
"contentMarkdown": "# ...",
"contentHtml": "<h1>...</h1>",
"seoMetadata": {
"title": "How to Improve AI Visibility",
"description": "..."
},
"coverImageUrl": "https://...",
"focusKeyword": "ai visibility",
"publishedAt": "2026-06-06T10:00:00.000Z",
"createdAt": "2026-06-05T16:00:00.000Z"
}
],
"total": 12,
"page": 1,
"limit": 20,
"hasMore": false
}/api/v1/blogs?slug={slug}Fetch one published blog by slug. This is useful when your CMS stores BobBuilds slugs or when your frontend resolves a single article on demand.
Parameters
slugqueryrequiredPublished blog slugExample
curl "https://your-bobbuilds-instance.com/api/v1/blogs?slug=how-to-improve-ai-visibility" \
-H "Authorization: Bearer bb_your_key_here"Response
{
"blog": {
"id": "blog_abc123",
"title": "How to Improve AI Visibility",
"slug": "how-to-improve-ai-visibility",
"contentMarkdown": "# ...",
"contentHtml": "<h1>...</h1>",
"seoMetadata": {
"title": "How to Improve AI Visibility",
"description": "..."
},
"coverImageUrl": "https://...",
"focusKeyword": "ai visibility",
"publishedAt": "2026-06-06T10:00:00.000Z",
"createdAt": "2026-06-05T16:00:00.000Z"
}
}/api/v1/blogs/{id}/publishMark a generated blog as published so it becomes available through the external GET endpoints. This is the approval step before your downstream CMS importer fetches it.
Parameters
idpathrequiredGenerated blog IDExample
curl -X POST https://your-bobbuilds-instance.com/api/v1/blogs/blog_abc123/publish \
-H "Authorization: Bearer bb_your_key_here"Response
{
"blog": {
"id": "blog_abc123",
"title": "How to Improve AI Visibility",
"slug": "how-to-improve-ai-visibility",
"contentMarkdown": "# ...",
"contentHtml": "<h1>...</h1>",
"seoMetadata": {
"title": "How to Improve AI Visibility",
"description": "..."
},
"coverImageUrl": "https://...",
"focusKeyword": "ai visibility",
"publishedAt": "2026-06-06T10:00:00.000Z",
"createdAt": "2026-06-05T16:00:00.000Z"
}
}/api/v1/blogs/{id}/deliverPush a published blog to a customer webhook or CMS ingestion endpoint. Set publish=true if you want BobBuilds to publish the blog first and then deliver it in the same call.
Parameters
idpathrequiredGenerated blog IDdestinationUrlbodyrequiredWebhook or API endpoint that should receive the blogpublishbodyPublish before delivery if the blog is still in ready statussigningSecretbodyOptional HMAC secret for X-BobBuilds-SignatureExample
curl -X POST https://your-bobbuilds-instance.com/api/v1/blogs/blog_abc123/deliver \
-H "Authorization: Bearer bb_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"destinationUrl": "https://customer-site.com/api/cms-ingest",
"publish": true,
"signingSecret": "replace-with-a-shared-secret",
"extraHeaders": {
"X-Customer-Workspace": "acme"
}
}'Response
{
"ok": true,
"delivery": {
"destinationUrl": "https://customer-site.com/api/cms-ingest",
"status": 200,
"statusText": "OK",
"body": "{\"received\":true}"
},
"blog": {
"id": "blog_abc123",
"title": "How to Improve AI Visibility",
"slug": "how-to-improve-ai-visibility",
"contentMarkdown": "# ...",
"contentHtml": "<h1>...</h1>",
"seoMetadata": {
"title": "How to Improve AI Visibility",
"description": "..."
},
"coverImageUrl": "https://...",
"focusKeyword": "ai visibility",
"publishedAt": "2026-06-06T10:00:00.000Z",
"createdAt": "2026-06-05T16:00:00.000Z"
}
}Example CMS sync flow
A common pattern is to run a sync script every few minutes, fetch published BobBuilds blogs, and create or update corresponding posts in the destination CMS.
const res = await fetch("https://your-bobbuilds-instance.com/api/v1/blogs", {
headers: {
Authorization: "Bearer bb_your_key_here"
}
});
const { blogs } = await res.json();
for (const blog of blogs) {
// Example: send to your CMS API
await fetch("https://your-cms.com/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: blog.title,
slug: blog.slug,
html: blog.contentHtml,
markdown: blog.contentMarkdown,
seo: blog.seoMetadata,
coverImageUrl: blog.coverImageUrl
})
});
}In other words: BobBuilds is the source system for generated content, and the customer's CMS is the destination system that receives it through API calls.
Webhook payload
The delivery endpoint sends a single event envelope with the blog data nested under blog.
{
"event": "blog.published",
"sentAt": "2026-06-06T10:00:00.000Z",
"blog": {
"id": "blog_abc123",
"title": "How to Improve AI Visibility",
"slug": "how-to-improve-ai-visibility",
"contentMarkdown": "# ...",
"contentHtml": "<h1>...</h1>",
"seoMetadata": {
"metaTitle": "How to Improve AI Visibility",
"metaDescription": "...",
"keywords": ["ai visibility", "geo"],
"wordCount": 1438
},
"coverImageUrl": "https://...",
"focusKeyword": "ai visibility",
"publishedAt": "2026-06-06T10:00:00.000Z",
"createdAt": "2026-06-05T16:00:00.000Z"
}
}Headers included on delivery: X-BobBuilds-Event, X-BobBuilds-Blog-Id, X-BobBuilds-Blog-Slug, and optionally X-BobBuilds-Signature.
CMS-specific examples
WordPress
await fetch("https://your-site.com/wp-json/wp/v2/posts", {
method: "POST",
headers: {
Authorization: "Basic " + btoa("username:application-password"),
"Content-Type": "application/json"
},
body: JSON.stringify({
title: blog.title,
slug: blog.slug,
status: "draft",
content: blog.contentHtml,
excerpt: blog.seoMetadata?.metaDescription ?? ""
})
});Webflow
await fetch("https://api.webflow.com/v2/collections/{collectionId}/items", {
method: "POST",
headers: {
Authorization: "Bearer WEBFLOW_API_TOKEN",
"Content-Type": "application/json"
},
body: JSON.stringify({
isArchived: false,
isDraft: true,
fieldData: {
name: blog.title,
slug: blog.slug,
postBody: blog.contentHtml,
summary: blog.seoMetadata?.metaDescription ?? ""
}
})
});Sanity
await fetch("https://{projectId}.api.sanity.io/{apiVersion}/data/mutate/{dataset}", {
method: "POST",
headers: {
Authorization: "Bearer SANITY_API_TOKEN",
"Content-Type": "application/json"
},
body: JSON.stringify({
mutations: [{
createOrReplace: {
_id: "bobbuilds." + blog.slug,
_type: "post",
title: blog.title,
slug: { _type: "slug", current: blog.slug },
contentHtml: blog.contentHtml,
contentMarkdown: blog.contentMarkdown,
seo: blog.seoMetadata ?? {},
publishedAt: blog.publishedAt
}
}]
})
});Verify webhook signatures
import { createHmac, timingSafeEqual } from "node:crypto";
function isValidSignature(rawBody, signatureHeader, signingSecret) {
if (!signatureHeader?.startsWith("sha256=")) return false;
const expected = createHmac("sha256", signingSecret)
.update(rawBody)
.digest("hex");
const received = signatureHeader.slice("sha256=".length);
return timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}Ready-to-run sync script
The repo includes scripts/sync-blogs-to-cms.mjs for teams that want a simple polling job instead of webhook delivery.
CMS_PROVIDER=wordpress \
BOBBUILDS_BASE_URL=https://your-bobbuilds-instance.com \
BOBBUILDS_API_KEY=bb_your_key_here \
WORDPRESS_BASE_URL=https://your-site.com \
WORDPRESS_USERNAME=admin \
WORDPRESS_APP_PASSWORD=xxxx xxxx xxxx xxxx \
node scripts/sync-blogs-to-cms.mjsSupported providers in the starter script: webhook, wordpress, webflow, and sanity.
Error Codes
| Status | Meaning |
|---|---|
| 401 | Missing or invalid API key |
| 404 | Blog not found or not published |
| 409 | Blog is still in ready status and must be published before delivery |
| 502 | Customer webhook or CMS ingestion endpoint rejected the delivery |
| 500 | Internal server error |