{
  "name": "Five Product Ad Images Under $0.20 with reAPI",
  "nodes": [
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -820,
        100
      ],
      "id": "69ea1ed0-34a0-47cc-8fbe-b4e6bb064cdf",
      "name": "When clicking Execute workflow"
    },
    {
      "parameters": {
        "jsCode": "const sourceImageUrl = 'https://reapi.ai/images/landing/gpt-image-2/use-case-0.png';\n\nconst briefs = [\n  {\n    concept: 'minimal studio',\n    headline: 'MORNING, MADE BETTER.',\n    direction: 'Place it on a pale stone pedestal in a bright minimal studio with soft morning shadows. Use clean large black editorial type in the upper-left.'\n  },\n  {\n    concept: 'rainy reading nook',\n    headline: 'SLOW DOWN. SIP.',\n    direction: 'Show it beside a rain-streaked window in a warm quiet reading nook, with natural steam and soft amber light. Use large cream serif type near the top.'\n  },\n  {\n    concept: 'cobalt graphic layout',\n    headline: 'YOUR DESK DESERVES BETTER.',\n    direction: 'Use a cobalt blue background, one safety-orange geometric accent, crisp hard shadows, and large white sans-serif type.'\n  },\n  {\n    concept: 'restrained gift scene',\n    headline: 'A MUG WORTH KEEPING.',\n    direction: 'Place it in an open kraft gift box with simple navy tissue paper and restrained warm lights. Use large dark navy editorial type at the top.'\n  },\n  {\n    concept: 'dark glaze detail',\n    headline: 'FROM FIRST SIP TO LAST.',\n    direction: 'Use a close three-quarter product view on a dark charcoal surface with a narrow warm spotlight. Use large warm-white serif type in the upper-left.'\n  }\n];\n\nreturn briefs.map((brief) => ({\n  json: {\n    ...brief,\n    source_image_url: sourceImageUrl,\n    prompt: `Create a 4:5 product advertising image using the reference ceramic mug as the exact product. Preserve the mug shape, right-side handle, two-tone teal and charcoal glaze, proportions, and surface texture. ${brief.direction} Add only this headline, exactly as written: \"${brief.headline}\" Keep generous safe margins. No logo, no other text, no extra mugs, no people.`\n  }\n}));"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -580,
        100
      ],
      "id": "62a6e444-ac70-4215-852d-10468b604730",
      "name": "Build Five Ad Briefs"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://reapi.ai/api/v1/images/generations",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { model: 'nano-banana-2-lite', prompt: $json.prompt, image_urls: [$json.source_image_url], aspect_ratio: '4:5' } }}",
        "options": {
          "timeout": 30000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -320,
        100
      ],
      "id": "ad0967af-bbce-4c80-af8c-149253304d58",
      "name": "Create Image Tasks",
      "notesInFlow": true,
      "notes": "After import, select your reAPI Bearer Auth credential. Do not enable automatic POST retries unless you also add an idempotency key."
    },
    {
      "parameters": {
        "resume": "timeInterval",
        "amount": 15,
        "unit": "seconds"
      },
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -60,
        100
      ],
      "id": "b095edaf-8528-4094-adcb-ae1e6d7624f5",
      "name": "Wait 15 Seconds",
      "webhookId": "aaed2bc3-6e6a-4bc3-a957-df2ca30dc1c9"
    },
    {
      "parameters": {
        "url": "={{ 'https://reapi.ai/api/v1/tasks/' + $json.id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "options": {
          "timeout": 30000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        200,
        100
      ],
      "id": "12a8742d-d462-4e9b-96d4-ecefc3a0e5b8",
      "name": "Get Task Status",
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000,
      "notesInFlow": true,
      "notes": "After import, select the same reAPI Bearer Auth credential used by Create Image Tasks. GET polling can be retried safely."
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "2f80790c-0fe8-4028-bab0-58d86de9ca72",
              "leftValue": "={{ $json.status }}",
              "rightValue": "completed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        460,
        100
      ],
      "id": "2d657916-1968-472c-aa82-268081b53c2b",
      "name": "Completed?"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "af5e7bb5-f18a-4639-83a8-73e82493a375",
              "leftValue": "={{ $json.status }}",
              "rightValue": "failed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        700,
        220
      ],
      "id": "0961b92d-3597-4f9a-9ea8-06f5d779e436",
      "name": "Failed?"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "5575225f-5b4d-497d-8634-81122d050119",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 19,
              "operator": {
                "type": "number",
                "operation": "gte"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        940,
        300
      ],
      "id": "60bbe762-5207-459d-942f-993407835388",
      "name": "Poll Limit Reached?",
      "notesInFlow": true,
      "notes": "$runIndex is zero-based. With this connection order, >= 19 stops after the twentieth incomplete status check."
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const brief = $('Build Five Ad Briefs').item.json;\nreturn {\n  json: {\n    concept: brief.concept,\n    requested_headline: brief.headline,\n    task_id: $json.id,\n    status: $json.status,\n    credits: $json.usage?.credits ?? null,\n    image_url: $json.output?.image_urls?.[0] ?? null\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        -40
      ],
      "id": "d07d53d2-8323-4fb4-b3fd-81f8586d3f86",
      "name": "Successful Images"
    },
    {
      "parameters": {
        "jsCode": "const task = $input.first().json;\nthrow new Error(`Image task ${task.id ?? 'unknown'} failed: ${task.error?.message ?? 'No error message returned'}`);"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        940,
        160
      ],
      "id": "8abfa43b-3c10-4b49-8088-98aaf1ac0278",
      "name": "Stop on Task Failure"
    },
    {
      "parameters": {
        "jsCode": "const tasks = $input.all().map((item) => item.json.id ?? 'unknown').join(', ');\nthrow new Error(`Polling stopped after 20 incomplete checks. Inspect these task IDs before retrying a POST: ${tasks}`);"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1180,
        240
      ],
      "id": "52af70ed-cf30-4bac-8e1b-11fa44c0b98c",
      "name": "Stop on Poll Timeout"
    }
  ],
  "connections": {
    "When clicking Execute workflow": {
      "main": [
        [
          {
            "node": "Build Five Ad Briefs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Five Ad Briefs": {
      "main": [
        [
          {
            "node": "Create Image Tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Image Tasks": {
      "main": [
        [
          {
            "node": "Wait 15 Seconds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 15 Seconds": {
      "main": [
        [
          {
            "node": "Get Task Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Task Status": {
      "main": [
        [
          {
            "node": "Completed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Completed?": {
      "main": [
        [
          {
            "node": "Successful Images",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Failed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Failed?": {
      "main": [
        [
          {
            "node": "Stop on Task Failure",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Poll Limit Reached?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poll Limit Reached?": {
      "main": [
        [
          {
            "node": "Stop on Poll Timeout",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait 15 Seconds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "active": false,
  "tags": []
}
