BlogProduce Studio
İçerik operasyonlarını tek panelden yönetin
BlogProduce API
Generate AI-written, SEO-ready blog posts and serve them anywhere — your website, app or CMS. REST, JSON, cursor pagination, idempotent writes.
https://api.blogproduce.comOverview
How the BlogProduce API works and what you can build with it.
The API is organized around REST. Every request and response is JSON. Successful responses share a single envelope:
{
"success": true,
"data": { ... }
}Asynchronous content generation
Blog generation is asynchronous: you queue a job, poll its status, then publish and fetch the finished post. A typical integration is four calls:
Queue
POST /api/v1/posts:generate returns a jobId instantly.
Poll
GET /api/v1/jobs/{jobId} until status is "succeeded".
Publish
POST /api/v1/posts/{postId}/publish makes it live.
Serve
GET /api/v1/published/{slug}?format=html on your site.
API Keys
Create and manage the keys your integration uses.
API Anahtarların
Anahtarlar workspace'e bağlıdır. Secret anahtar yalnızca oluşturulduğu anda gösterilir.
Authentication
Authenticate every request with your secret API key.
Create a key from the panel above. The secret key (prefixed bg_secret_) is shown once at creation. Send it on every request — the recommended way is the Authorization header:
curl https://api.blogproduce.com/api/v1/posts \
-H "Authorization: Bearer bg_secret_YOUR_KEY"Keys are scoped to a workspace: every request only sees that workspace's posts. Revoking a key disables it immediately.
Quickstart
Generate, publish and fetch your first article in under five minutes.
const BASE_URL = 'https://api.blogproduce.com';
const API_KEY = process.env.BLOGPRODUCE_API_KEY; // bg_secret_...
async function api(path, options = {}) {
const res = await fetch(BASE_URL + path, {
...options,
headers: {
'Authorization': 'Bearer ' + API_KEY,
'Content-Type': 'application/json',
...options.headers,
},
});
const body = await res.json();
if (!res.ok || body.success === false) {
throw new Error(body.message || 'Request failed: ' + res.status);
}
return body.data;
}
// 1) Queue a blog post
const { jobId } = await api('/api/v1/posts:generate', {
method: 'POST',
headers: { 'Idempotency-Key': 'first-post-001' },
body: JSON.stringify({
topic: 'How to choose a CRM for a small business',
language: 'en',
tone: 'professional',
length: 'medium',
model: 'pro', // 'standard' | 'pro' | 'premium'
seoMode: true,
}),
});
// 2) Poll until the job finishes (typically 30-90 seconds)
let job;
do {
await new Promise((r) => setTimeout(r, 4000));
job = await api('/api/v1/jobs/' + jobId);
} while (job.status === 'queued' || job.status === 'running');
if (job.status !== 'succeeded') {
throw new Error('Generation failed: ' + (job.error || 'unknown'));
}
// 3) Publish it
const published = await api('/api/v1/posts/' + job.postId + '/publish', {
method: 'POST',
body: JSON.stringify({}),
});
// 4) Fetch the finished article as HTML
const post = await api('/api/v1/published/' + published.slug + '?format=html');
console.log(post.title);
console.log(post.content_html);Generate Content
Queue an AI blog generation job. Returns immediately with a job id.
/api/v1/posts:generateAuthorizationstringrequired | Bearer bg_secret_... — your secret API key. |
Idempotency-Keystringoptional | Recommended. Retrying with the same key and identical body within 24h returns the original response instead of creating a duplicate job. |
topicstring (5-200 chars)required | What the article should be about. The more specific, the better the result. |
languagestringoptional | ISO code: 'tr', 'en', 'de', 'fr', 'es' and 30+ more. Default: 'tr'. |
tonestring (≤100 chars)optional | Writing tone, e.g. 'professional', 'friendly', 'authoritative'. Default: 'professional'. |
lengthstringoptional | 'short' (~600-900 words), 'medium' (~1100-1500), 'long' (~1800-2600). Default: 'medium'. |
modelstringoptional | Quality tier: 'standard' (fast), 'pro' (higher quality), 'premium' (Claude Opus — best long-form quality). Default: 'standard'. |
seoModebooleanoptional | Adds SEO optimization: meta tags, FAQ targeting long-tail queries, keyword placement, JSON-LD schema. |
additionalContextstring (≤1000 chars)optional | Brand facts, product details or angle instructions the writer must respect. |
targetAudiencestring (≤100 chars)optional | Who the article is for, e.g. 'startup founders'. |
keywordsstring[]optional | Target keywords to weave in naturally. |
outlinestringoptional | Optional outline to follow instead of letting the AI plan the structure. |
coverImageUrlstringoptional | Pre-selected cover image URL to attach to the post. |
{
"success": true,
"data": {
"jobId": "3f6b2c1e-8a4d-4c2f-9e1b-7d5a2c8f4e6a",
"status": "queued"
}
}Job Status
Poll a generation job until it finishes.
/api/v1/jobs/{jobId}{
"success": true,
"data": {
"status": "succeeded", // queued | running | succeeded | failed | cancelled
"postId": "9c8d7e6f-...", // present once the post exists
"error": "..." // present only when status is "failed"
}
}status is succeeded, use postId to review, update or publish the post.Posts
List, read, edit and publish the posts in your workspace.
List posts
/api/v1/postsstatusstringoptional | Filter by status: queued | generating | draft | review | published | failed. |
limitnumberoptional | Items per page. Default 50, max 100. |
cursorstringoptional | Opaque cursor from the previous response for the next page. |
{
"success": true,
"data": {
"posts": [
{
"id": "9c8d7e6f-...",
"title": "How to Choose a CRM for a Small Business",
"slug": "how-to-choose-a-crm-for-a-small-business",
"excerpt": "...",
"status": "draft",
"language": "en",
"coverImageUrl": null,
"publishedAt": null,
"createdAt": "2026-08-04T10:12:00.000Z",
"updatedAt": "2026-08-04T10:13:30.000Z",
"author": { "name": "...", "email": "..." },
"tags": []
}
],
"cursor": "MjAyNi0wOC0wNFQxMDoxMjowMC4wMDBa",
"hasMore": true
}
}data.cursor back as ?cursor= to fetch the next page. When hasMore is false you have everything.Get a post
/api/v1/posts/{id}?format=json|md|html{
"success": true,
"data": {
"id": "9c8d7e6f-...",
"title": "...",
"slug": "...",
"excerpt": "...",
"content": "<h1>...</h1><p>...</p>", // in the requested format
"contentFormat": "html",
"content_html": "<h1>...</h1><p>...</p>", // always-available HTML rendering
"status": "draft",
"language": "en",
"coverImageUrl": null,
"publishedAt": null,
"createdAt": "2026-08-04T10:12:00.000Z",
"updatedAt": "2026-08-04T10:14:00.000Z",
"author": { "name": "...", "email": "..." },
"tags": [],
"seo": {
"metaTitle": "...",
"metaDescription": "...",
"ogTitle": "...",
"ogDescription": "...",
"schemaJsonld": { "@context": "https://schema.org" }
}
}
}Update a post
/api/v1/posts/{id}titlestring (≤200)optional | New title. |
excerptstring (≤500)optional | New excerpt. |
contentstringoptional | Replacement content (stored as a new revision). |
slugstringoptional | New slug — must be unique within the workspace. |
draft, generated or review.Delete a post
/api/v1/posts/{id}curl -X DELETE "https://api.blogproduce.com/api/v1/posts/POST_ID" \
-H "Authorization: Bearer bg_secret_YOUR_KEY"
# 200 OK
# {
# "success": true,
# "data": { "id": "9c8d7e6f-...", "deleted": true }
# }404.Publish a post
/api/v1/posts/{id}/publishcurl -X POST "https://api.blogproduce.com/api/v1/posts/POST_ID/publish" \
-H "Authorization: Bearer bg_secret_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "slug": "optional-custom-slug" }'
# 200 OK
# {
# "success": true,
# "data": {
# "id": "9c8d7e6f-...",
# "status": "published",
# "slug": "how-to-choose-a-crm-for-a-small-business",
# "publishedAt": "2026-08-04T10:20:00.000Z",
# "message": "Post published successfully"
# }
# }Published Content
Fetch only live content — ideal for rendering your blog.
/api/v1/published?limit=50&cursor=.../api/v1/published/{slug}?format=json|md|html// npm i isomorphic-dompurify
import DOMPurify from 'isomorphic-dompurify';
// app/blog/page.tsx — list published posts (server component)
export default async function BlogPage() {
const res = await fetch('https://api.blogproduce.com/api/v1/published?limit=20', {
headers: { Authorization: `Bearer ${process.env.BLOGPRODUCE_API_KEY}` },
next: { revalidate: 300 }, // ISR: refresh every 5 minutes
});
const { data } = await res.json();
const posts = data.posts; // data also carries { cursor, hasMore }
return (
<main>
{posts.map((post) => (
<a key={post.id} href={`/blog/${post.slug}`}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</a>
))}
</main>
);
}
// app/blog/[slug]/page.tsx — render a single post
export default async function PostPage({ params }) {
const res = await fetch(
`https://api.blogproduce.com/api/v1/published/${params.slug}?format=html`,
{
headers: { Authorization: `Bearer ${process.env.BLOGPRODUCE_API_KEY}` },
next: { revalidate: 300 },
},
);
const { data: post } = await res.json();
// content_html is sanitized server-side, but sanitize again at the render
// boundary — it is the one place that protects you regardless of what
// reaches the content upstream.
const html = DOMPurify.sanitize(post.content_html);
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: html }} />
</article>
);
}content_html is sanitized server-side before delivery. The seo object contains ready-to-use meta tags and JSON-LD — wire them into your page <head> for full SEO benefit.Public API (No Auth)
Read-only endpoints for client-side rendering — no API key required.
Safe to call directly from the browser: they expose only published posts of a project, addressed by its public project slug (shown in your workspace settings).
/api/v1/public/projects/{projectSlug}/posts?limit=50&cursor=.../api/v1/public/projects/{projectSlug}/posts/{slug}?format=html// No key needed — safe for client-side code
const res = await fetch(
'https://api.blogproduce.com/api/v1/public/projects/YOUR_PROJECT_SLUG/posts?limit=10',
);
const { data } = await res.json();
for (const post of data.posts) {
console.log(post.title, post.slug, post.publishedAt);
}
// data.cursor + data.hasMore -> cursor pagination, same as the Posts APIContent Formats
Every post is stored canonically and converted on demand.
Endpoints that return full content accept a ?format= query parameter:
jsondefaultoptional | Canonical structure: { "content": "<markdown>", "metadata": { "toc": [...], "faq": [...], "seo": {...} } }. Best when you want structured access to FAQ, table of contents and SEO fields. |
mdmarkdownoptional | The article as plain Markdown — ideal for static site generators or storing in your own CMS. |
htmlhtmloptional | Sanitized, render-ready HTML. Responses also always include content_html regardless of the requested format. |
Errors
Standard HTTP status codes with a JSON body.
{
"statusCode": 400,
"message": "topic must be longer than or equal to 5 characters",
"error": "Bad Request"
}400Bad Requestoptional | Validation failed — the message field lists what is wrong. Unknown body fields are also rejected. |
401Unauthorizedoptional | Missing, invalid, expired or revoked API key. |
403Forbiddenoptional | The key is valid but not allowed to perform this action. |
404Not Foundoptional | Resource does not exist or belongs to another workspace. |
429Too Many Requestsoptional | Rate limit exceeded — check the rate limit headers and retry with backoff. |
500Server Erroroptional | Something went wrong on our side. Safe to retry with the same Idempotency-Key. |
Rate Limits
Per-key limits keep the platform fast for everyone.
Requests are rate-limited per API key. Current usage is reported on responses via headers:
X-RateLimit-Limit: 150
X-RateLimit-Remaining: 149429, wait and retry with exponential backoff. Generation calls also count against your plan's monthly content quota — see the Usage page.