{
  "name": "ReelsBuilder: stock video to post-ready output",
  "nodes": [
    {
      "parameters": {
        "content": "## ReelsBuilder: stock video -> post-ready output\n\n1. Create a **Header Auth** credential: Name `Authorization`, Value `Bearer rb_...` (your key from reelsbuilder.ai/dashboard/v5/settings?tab=api). Select it on the three HTTP Request nodes. Never paste the key into a node field.\n2. Edit the **Brief** node (prompt, brand, max price).\n3. Run. The dry run is free; the create call costs $0.60 and is refunded automatically if the render fails.\n\nPolling: 15s, growing to 60s, stops after 30 min. Prefer `webhook_url` + a Webhook trigger workflow for many videos. Docs: https://reelsbuilder.ai/docs/api/workflows",
        "height": 360,
        "width": 520
      },
      "id": "a0000000-0000-4000-8000-000000000001",
      "name": "Read me",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -260,
        -420
      ]
    },
    {
      "parameters": {},
      "id": "a0000000-0000-4000-8000-000000000002",
      "name": "Run manually",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -260,
        0
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1,
              "triggerAtHour": 9
            }
          ]
        }
      },
      "id": "a0000000-0000-4000-8000-000000000003",
      "name": "Every day 09:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -260,
        180
      ]
    },
    {
      "parameters": {
        "jsCode": "// One video per run. Edit this brief (or replace this node with your sheet/CRM/RSS input).\n// Field names are the real /api/v1/generate-and-post fields; see\n// https://api.reelsbuilder.ai/api/v1/capabilities for every option.\nconst body = {\n  prompt: \"Why mid-size IT teams are moving to zero-trust networking, and what it changes for engineers\",\n  content_purpose: \"education\",        // education | sales | awareness | product_demo | case_study | news | entertainment\n  clip_type: \"tech\",                   // footage niche; omit to infer from brand.industry\n  duration_sec: 30,                    // 15-120, same price\n  aspect_ratio: \"9:16\",                // stock is 9:16 only\n  include_branding: false,             // hide the ReelsBuilder watermark (paid plans)\n  cta: { text: \"Book a demo at acme.com\" },\n  // Per-client branding. When sent it REPLACES the account brand (nothing is merged).\n  brand: {\n    name: \"Acme Cloud\",\n    website_url: \"https://acme.com\",\n    logo_url: \"https://acme.com/logo.png\",\n    primary_color: \"#0A66C2\",\n    accent_color: \"#00C2FF\",\n    tone: \"professional\",\n    industry: \"SaaS\",\n  },\n  // Optional: one signed callback when the job ends (public https URL).\n  // webhook_url: \"https://your-n8n.example.com/webhook/reelsbuilder\",\n};\n\nreturn [{\n  json: {\n    body,\n    // Stop before paying if the dry-run quote is above this (stock is 60 = $0.60).\n    max_price_usd_cents: 60,\n    // Stable id for this creative. Same id = same Idempotency-Key = never billed twice.\n    // Leave empty to use the n8n execution id (one video per execution).\n    creative_id: \"\",\n  },\n}];\n"
      },
      "id": "a0000000-0000-4000-8000-000000000004",
      "name": "Brief",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        0,
        80
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.reelsbuilder.ai/api/v1/generate-and-post",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {},
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify(Object.assign({}, $json.body, { dry_run: true })) }}"
      },
      "id": "a0000000-0000-4000-8000-000000000005",
      "name": "Quote (dry run, free)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        240,
        80
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.data.valid }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            },
            {
              "id": "c2",
              "leftValue": "={{ $json.data.price.usd_cents }}",
              "rightValue": "={{ $('Brief').item.json.max_price_usd_cents }}",
              "operator": {
                "type": "number",
                "operation": "lte"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a0000000-0000-4000-8000-000000000006",
      "name": "Price OK?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        480,
        80
      ]
    },
    {
      "parameters": {
        "errorMessage": "={{ 'Quote ' + $json.data.price.display + ' is above max_price_usd_cents; nothing was charged.' }}"
      },
      "id": "a0000000-0000-4000-8000-000000000007",
      "name": "Over budget",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        720,
        260
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.reelsbuilder.ai/api/v1/generate-and-post",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Idempotency-Key",
              "value": "={{ $('Brief').item.json.creative_id ? 'n8n-' + $('Brief').item.json.creative_id : 'n8n-' + $workflow.id + '-' + $execution.id }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($('Brief').item.json.body) }}"
      },
      "id": "a0000000-0000-4000-8000-000000000008",
      "name": "Create video",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        720,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "// Receipt: data.job_id and data.poll_url are always present.\nconst receipt = $input.first().json.data;\nreturn [{\n  json: {\n    job_id: receipt.job_id,\n    poll_url: receipt.poll_url,\n    price_usd_cents: receipt.price_usd_cents,\n    started_at: Date.now(),\n    wait_sec: 15,\n    polls: 0,\n  },\n}];\n"
      },
      "id": "a0000000-0000-4000-8000-000000000009",
      "name": "Start polling",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        0
      ]
    },
    {
      "parameters": {
        "amount": "={{ $json.wait_sec }}",
        "unit": "seconds"
      },
      "id": "a0000000-0000-4000-8000-000000000010",
      "name": "Wait",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1200,
        0
      ],
      "webhookId": "a0000000-0000-4000-8000-0000000000aa"
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.reelsbuilder.ai/api/v1/jobs/{{ $json.job_id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "a0000000-0000-4000-8000-000000000011",
      "name": "Get job",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1440,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "// Backoff: 15s, 23s, 35s, 53s, then every 60s. Gives up after 30 minutes.\nconst state = $('Wait').first().json;\nconst job = $input.first().json.data.job;\nconst terminal = ['completed', 'failed', 'cancelled', 'dead_lettered'];\nconst done = terminal.includes(job.status);\nconst elapsedSec = (Date.now() - state.started_at) / 1000;\nreturn [{\n  json: {\n    ...state,\n    status: job.status,\n    job,\n    done,\n    timed_out: !done && elapsedSec > 1800,\n    polls: state.polls + 1,\n    wait_sec: Math.min(60, Math.round(state.wait_sec * 1.5)),\n  },\n}];\n"
      },
      "id": "a0000000-0000-4000-8000-000000000012",
      "name": "Evaluate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1680,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "c3",
              "leftValue": "={{ $json.done }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a0000000-0000-4000-8000-000000000013",
      "name": "Finished?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1920,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "c4",
              "leftValue": "={{ $json.status }}",
              "rightValue": "completed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a0000000-0000-4000-8000-000000000014",
      "name": "Completed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2160,
        -100
      ]
    },
    {
      "parameters": {
        "jsCode": "// Post-ready output. artifacts.description already includes the hashtags;\n// they are also split out here for tools that want them separately.\nconst job = $input.first().json.job;\nconst a = job.artifacts || {};\nconst description = a.description || '';\nconst hashtags = description.match(/#[\\p{L}\\p{N}_]+/gu) || [];\nreturn [{\n  json: {\n    job_id: job.id,\n    video_url: a.videoUrl,\n    video_urls: a.videoUrls || [],\n    thumbnail_url: a.thumbnailUrl,\n    title: a.title,\n    description,\n    hashtags,\n    duration_sec: a.durationSec,\n    note: 'Copy the MP4 into your own storage; CDN files have a retention window.',\n  },\n}];\n"
      },
      "id": "a0000000-0000-4000-8000-000000000015",
      "name": "Post-ready output",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2400,
        -200
      ]
    },
    {
      "parameters": {
        "errorMessage": "={{ 'Job ' + $json.job_id + ' ' + $json.status + ': ' + ($json.job.error ? $json.job.error.code + ' - ' + $json.job.error.message + ' (retryable: ' + $json.job.error.retryable + '). Charges are refunded; resubmit with a NEW creative_id only if retryable.' : 'no error detail') }}"
      },
      "id": "a0000000-0000-4000-8000-000000000016",
      "name": "Job failed",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        2400,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "c5",
              "leftValue": "={{ $json.timed_out }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a0000000-0000-4000-8000-000000000017",
      "name": "Timed out?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2160,
        160
      ]
    },
    {
      "parameters": {
        "errorMessage": "={{ 'Job ' + $json.job_id + ' is still ' + $json.status + ' after 30 minutes. Check GET /api/v1/jobs/' + $json.job_id + ' later or contact admin@reelsbuilder.ai with the job id.' }}"
      },
      "id": "a0000000-0000-4000-8000-000000000018",
      "name": "Still running after 30 min",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        2400,
        260
      ]
    }
  ],
  "connections": {
    "Run manually": {
      "main": [
        [
          {
            "node": "Brief",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every day 09:00": {
      "main": [
        [
          {
            "node": "Brief",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Brief": {
      "main": [
        [
          {
            "node": "Quote (dry run, free)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Quote (dry run, free)": {
      "main": [
        [
          {
            "node": "Price OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Price OK?": {
      "main": [
        [
          {
            "node": "Create video",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Over budget",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create video": {
      "main": [
        [
          {
            "node": "Start polling",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start polling": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait": {
      "main": [
        [
          {
            "node": "Get job",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get job": {
      "main": [
        [
          {
            "node": "Evaluate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate": {
      "main": [
        [
          {
            "node": "Finished?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Finished?": {
      "main": [
        [
          {
            "node": "Completed?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Timed out?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Completed?": {
      "main": [
        [
          {
            "node": "Post-ready output",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Job failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Timed out?": {
      "main": [
        [
          {
            "node": "Still running after 30 min",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
