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/v1

Authentication

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_here

These 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

StatusMeaning
generatingBlog generation is in progress
readyContent exists but has not been published yet
publishedVisible through the external REST API and ready for CMS sync

Endpoints

GET/api/v1/blogs

List 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 1
limitqueryResults per page, max 100, default 20

Example

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
}
GET/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 slug

Example

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"
 }
}
POST/api/v1/blogs/{id}/publish

Mark 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 ID

Example

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"
 }
}
POST/api/v1/blogs/{id}/deliver

Push 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 ID
destinationUrlbodyrequiredWebhook or API endpoint that should receive the blog
publishbodyPublish before delivery if the blog is still in ready status
signingSecretbodyOptional HMAC secret for X-BobBuilds-Signature

Example

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.mjs

Supported providers in the starter script: webhook, wordpress, webflow, and sanity.

Error Codes

StatusMeaning
401Missing or invalid API key
404Blog not found or not published
409Blog is still in ready status and must be published before delivery
502Customer webhook or CMS ingestion endpoint rejected the delivery
500Internal server error