Skip to content
Dashboard

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