Workflows
End-to-end automation recipes for common content creation pipelines.
Article Pipeline
The most common workflow: pick a project, generate an article, wait for it to finish, then create social posts from it and publish them. Every generation needs a project_id, so listing projects is always step one. Projects are created in the app, not over the API.
A finished article has status cms or needs_editing. There is no published status. The simplest check is the is_complete boolean on the status endpoint.
const API_KEY = 'bp_your_key';
const BASE = 'https://www.brainpercent.app/api/v1';
const headers = {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
};
async function articlePipeline(topic, platforms = ['twitter', 'linkedin']) {
// Step 1: Pick a project. project_id is required for generation and
// carries the business context and the output language.
const projectsRes = await fetch(`${BASE}/projects?limit=1`, { headers });
const { data: projects } = await projectsRes.json();
if (!projects.length) throw new Error('Create a project in the app first');
const projectId = projects[0].id;
console.log(`Project: ${projects[0].business_name}`);
// Step 2: Generate article (1.5 credits). Returns 202 + poll_url.
const genRes = await fetch(`${BASE}/articles/generate`, {
method: 'POST',
headers,
body: JSON.stringify({
topic,
project_id: projectId,
tone: 'professional',
word_count: 1200,
}),
});
const { data: genData } = await genRes.json();
const articleId = genData.article_id;
// Step 3: Poll until the job finishes. Articles take roughly 10-15 minutes.
let article;
while (true) {
const statusRes = await fetch(`${BASE}/articles/${articleId}/status`, { headers });
const { data: status } = await statusRes.json();
if (status.status === 'failed') throw new Error('Article generation failed');
// is_complete covers both finished states: 'cms' and 'needs_editing'.
if (status.is_complete) {
const articleRes = await fetch(`${BASE}/articles/${articleId}`, { headers });
article = (await articleRes.json()).data;
break;
}
console.log(`Progress: ${status.progress}%`);
await new Promise(r => setTimeout(r, 15000));
}
console.log(`Article ready: ${article.title}`);
// Step 4: Generate social content (0.6 credits per platform).
// One request covers every platform and returns ONE content_id.
const socialRes = await fetch(`${BASE}/social/generate`, {
method: 'POST',
headers,
body: JSON.stringify({
source_url: `https://www.brainpercent.app/articles/${article.slug}`,
platforms,
project_id: projectId,
angle: 'Practical takeaways for busy marketers',
}),
});
const { data: socialData } = await socialRes.json();
const contentId = socialData.content_id;
// Step 5: Poll the single content row until it is done.
let content;
while (true) {
const res = await fetch(`${BASE}/social/content/${contentId}`, { headers });
content = (await res.json()).data;
if (content.status === 'ready' || content.status === 'completed') break;
if (content.status === 'failed') throw new Error('Social generation failed');
await new Promise(r => setTimeout(r, 10000));
}
// Captions live in the platforms object, keyed by platform name.
for (const [platform, caption] of Object.entries(content.platforms)) {
console.log(`${platform}: ${String(caption).slice(0, 80)}`);
}
// Step 6: Publish. platforms is optional; omit it to publish every variant.
await fetch(`${BASE}/social/publish`, {
method: 'POST',
headers,
body: JSON.stringify({ content_id: contentId, platforms }),
});
return { article, content };
}
// Usage
articlePipeline('Content marketing trends in 2026');Content Calendar
Walk every finished article in a project and turn each one into scheduled social posts. Finished articles are the ones with status cms. Passing scheduled_at to the publish endpoint schedules the post instead of sending it right away.
async function contentCalendar(apiKey, projectId) {
const BASE = 'https://www.brainpercent.app/api/v1';
const headers = {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
};
// Get project details. Projects carry business context, not generic
// name/description fields.
const projectRes = await fetch(`${BASE}/projects/${projectId}`, { headers });
const project = (await projectRes.json()).data;
console.log(`${project.business_name} (${project.business_language}) - ${project.expert_in}`);
// Get finished articles in the project. 'cms' is the completed status.
const articlesRes = await fetch(
`${BASE}/articles?project_id=${projectId}&status=cms&limit=100`,
{ headers }
);
const { data: articles } = await articlesRes.json();
// One social job per article, scheduled a day apart.
let dayOffset = 1;
for (const article of articles) {
const socialRes = await fetch(`${BASE}/social/generate`, {
method: 'POST',
headers,
body: JSON.stringify({
source_url: `https://www.brainpercent.app/articles/${article.slug}`,
platforms: ['twitter', 'linkedin'],
project_id: projectId,
}),
});
const { data } = await socialRes.json();
// Schedule once the content is ready (poll data.poll_url first).
const runAt = new Date(Date.now() + dayOffset * 86400000).toISOString();
console.log(`${article.title} -> content ${data.content_id}, schedule ${runAt}`);
dayOffset++;
}
}Credit-Aware Generation
Check your balance before generating so you only start jobs you can pay for. It is one balance for everything: an article costs 1.5 credits, social generation costs 0.6 credits per platform.
async function creditAwareGenerate(apiKey, projectId, topics) {
const BASE = 'https://www.brainpercent.app/api/v1';
const headers = { 'Authorization': `Bearer ${apiKey}` };
// Check available credits
const creditsRes = await fetch(`${BASE}/user/credits`, { headers });
const { data: credits } = await creditsRes.json();
const available = credits.available_credits;
// 1.5 credits per article.
const costPerArticle = 1.5;
const maxArticles = Math.floor(available / costPerArticle);
console.log(`Available: ${available} credits (can generate ${maxArticles} articles)`);
// Generate only what we can afford
const toGenerate = topics.slice(0, maxArticles);
const results = [];
for (const topic of toGenerate) {
const res = await fetch(`${BASE}/articles/generate`, {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify({ topic, project_id: projectId }),
});
const body = await res.json();
// A 402 means the balance moved under us. Stop, do not retry.
if (!body.success && body.error.code === 'INSUFFICIENT_CREDITS') break;
results.push(body);
console.log(`Started: ${topic} (${body.data.credits_remaining} credits left)`);
}
if (topics.length > results.length) {
console.warn(`Skipped ${topics.length - results.length} topics due to insufficient credits`);
}
return results;
}Bulk Operations
Page through every project and count its articles. The status filter on projects is a free-form string: production holds values such as active, completed, ready, pending, and generating. Omit it if you want everything.
async function auditProjects(apiKey) {
const BASE = 'https://www.brainpercent.app/api/v1';
const headers = { 'Authorization': `Bearer ${apiKey}` };
// Fetch all projects
let page = 1;
const projects = [];
while (true) {
const res = await fetch(`${BASE}/projects?page=${page}&limit=100`, { headers });
const { data, pagination } = await res.json();
projects.push(...data);
if (!pagination.has_more) break;
page++;
}
console.log(`Found ${projects.length} projects`);
// Count finished articles per project
for (const project of projects) {
const res = await fetch(
`${BASE}/articles?project_id=${project.id}&status=cms&limit=1`,
{ headers }
);
const { pagination } = await res.json();
console.log(`${project.business_name} (${project.status}): ${pagination.total} finished articles`);
}
}Next Steps
- Explore integration guides for Zapier, Make.com, n8n, and Claude via MCP
- Read Async Jobs and Polling for detailed polling strategies
- Understand costs with the Credits System guide
- Manage throughput using Rate Limits best practices