Skip to content
← All templates

AI Content Pipeline with Human Approval

Generate blog posts, social media content, or marketing copy with an LLM, then route through human approval before publishing. Rejected content goes back for revision automatically.

llm_callhuman_reviewrouterloophttp_request

What this workflow does

01

Generate content LLM generates content based on topic, audience, tone, and format from context. Uses structured output for title, body, meta description.

02

Quality check Second LLM call scores the content on relevance, grammar, and brand voice. Returns a quality score 0-1.

03

Auto-reject low quality Router checks if quality score < 0.6. If so, loops back to regenerate with feedback from the quality check. Max 3 iterations.

04

Human review Content with quality >= 0.6 goes to a human reviewer. Reviewer sees the generated content and quality score. Choices: Approve, Request changes, Reject.

05

Route by decision Router branches on reviewer decision. "Approve" goes to publish. "Request changes" loops back to regenerate with reviewer's comment as feedback. "Reject" ends the workflow.

06

Publish HTTP POST to your CMS API (WordPress, Ghost, custom) with the approved content.

Workflow definition

Copy this JSON and POST it to /sequences.

{
  "id": "ai_content_pipeline_v1",
  "context_schema": {
    "topic": "string",
    "audience": "string",
    "tone": "string",
    "format": "string",
    "cms_api_url": "string"
  },
  "blocks": [
    {
      "id": "generate_loop",
      "type": "loop",
      "condition": "steps.publish == null",
      "max_iterations": 3,
      "blocks": [
        {
          "id": "generate",
          "type": "step",
          "handler": "llm_call",
          "params": {
            "provider": "anthropic",
            "model": "claude-sonnet-4-20250514",
            "api_key_env": "ANTHROPIC_API_KEY",
            "system": "You are a content writer. Generate content in JSON: {\"title\": \"...\", \"body\": \"...\", \"meta_description\": \"...\"}. If revision feedback is provided, incorporate it.",
            "messages": [
              {
                "role": "user",
                "content": "Topic: {{context.data.topic}}\nAudience: {{context.data.audience}}\nTone: {{context.data.tone}}\nFormat: {{context.data.format}}{{#if context.data.revision_feedback}}\n\nRevision feedback: {{context.data.revision_feedback}}{{/if}}"
              }
            ]
          }
        },
        {
          "id": "quality_check",
          "type": "step",
          "handler": "llm_call",
          "params": {
            "provider": "anthropic",
            "model": "claude-haiku-4-5-20251001",
            "api_key_env": "ANTHROPIC_API_KEY",
            "system": "Score this content 0-1 on relevance, grammar, and brand voice. Return JSON: {\"score\": 0.85, \"feedback\": \"...\"}",
            "messages": [
              {
                "role": "user",
                "content": "{{steps.generate.output.parsed}}"
              }
            ]
          }
        },
        {
          "id": "quality_gate",
          "type": "router",
          "routes": [
            {
              "condition": "steps.quality_check.output.parsed.score < 0.6",
              "blocks": [
                {
                  "id": "store_feedback",
                  "type": "step",
                  "handler": "set_context",
                  "params": {
                    "revision_feedback": "{{steps.quality_check.output.parsed.feedback}}"
                  }
                }
              ]
            },
            {
              "default": true,
              "blocks": [
                {
                  "id": "review",
                  "type": "step",
                  "handler": "human_review",
                  "params": {
                    "review_data": "{{steps.generate.output.parsed}}",
                    "instructions": "Review generated {{context.data.format}} about '{{context.data.topic}}'. Quality score: {{steps.quality_check.output.parsed.score}}"
                  },
                  "wait_for_input": {
                    "prompt": "Review this {{context.data.format}} for publishing.\n\nTitle: {{steps.generate.output.parsed.title}}\n\nQuality score: {{steps.quality_check.output.parsed.score}}/1.0",
                    "choices": [
                      { "label": "Approve & Publish", "value": "approve" },
                      { "label": "Request Changes", "value": "revise" },
                      { "label": "Reject", "value": "reject" }
                    ],
                    "store_as": "review_decision",
                    "allow_comment": true
                  }
                },
                {
                  "id": "route_decision",
                  "type": "router",
                  "routes": [
                    {
                      "condition": "context.data.review_decision == 'approve'",
                      "blocks": [
                        {
                          "id": "publish",
                          "type": "step",
                          "handler": "http_request",
                          "params": {
                            "method": "POST",
                            "url": "{{context.data.cms_api_url}}",
                            "headers": { "Authorization": "Bearer credentials://cms-api-key" },
                            "body": "{{steps.generate.output.parsed}}"
                          },
                          "retry": { "max_attempts": 3, "backoff": "5s" }
                        }
                      ]
                    },
                    {
                      "condition": "context.data.review_decision == 'revise'",
                      "blocks": [
                        {
                          "id": "store_revision",
                          "type": "step",
                          "handler": "set_context",
                          "params": {
                            "revision_feedback": "{{context.data.review_decision_comment}}"
                          }
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Credentials to configure

anthropicapi_keyAnthropic API key for content generation and quality scoring
cms-api-keybearer_tokenAPI key for your CMS (WordPress, Ghost, Strapi, or custom)

How to trigger

// POST /sequences/ai_content_pipeline_v1/instances
{
  "context": {
    "data": {
      "topic": "How to implement durable workflows in production",
      "audience": "Backend engineers",
      "tone": "Technical but approachable",
      "format": "blog_post",
      "cms_api_url": "https://your-cms.com/api/posts"
    }
  }
}

Create a new workflow instance with your content brief. The pipeline handles generation, quality checks, human approval, and publishing automatically.

Customize it

Swap LLM provider — change provider to openai or google. Both generation and quality check can use different providers.

Add SEO optimization — insert an SEO analysis step between quality check and human review that checks keyword density and readability.

Multi-format — use Router to branch by format field: blog_post, social_media, email_newsletter — each with a different system prompt.

Image generation — add a parallel step that generates a hero image via DALL-E or Stable Diffusion while human review is pending.