{
  "name": "Bulk product videos from Google Sheets with Sisif",
  "nodes": [
    {
      "id": "2d1a8980-7b9f-44fb-b48e-ef9114d71ab9",
      "name": "Setup instructions",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1100,
        -560
      ],
      "parameters": {
        "content": "## Bulk product videos from Google Sheets\nTurns every pending row of a product sheet into a short vertical product video and writes the video link back.\n\n### Setup\n1. Create a sheet with the columns: **image_url**, **description**, **status**, **video_id**, **video_url** (plus a **row_number** is added automatically by n8n).\n2. Connect **Google Sheets** credentials and pick your document and sheet in **Read Pending Rows**, **Save Video Result** and **Mark Row Failed** (and in the trigger, if you use it).\n3. Connect an **OpenAI** credential on **OpenAI Chat Model** (any chat model works).\n4. Create a Sisif API key at https://sisif.ai/users/api-keys/ and add it as a **Bearer Auth** credential named *Sisif API key* on the three Sisif nodes.\n5. Run **Run Manually** to test with a couple of rows, then enable the sheet trigger if you want new rows processed automatically.\n\n### Rate limiting\nRows are processed one at a time. **Throttle Between Rows** waits before each new generation; raise it if you hit plan limits. **Wait Before Status Check** controls the polling interval.",
        "width": 560,
        "height": 600,
        "color": 4
      }
    },
    {
      "id": "28c29592-0533-4d3d-818f-933ba8bfd749",
      "name": "Image vs text note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -260,
        -380
      ],
      "parameters": {
        "content": "### Image or text\nIf **image_url** is an https URL, Sisif animates that photo (duration and resolution are fixed by the clip offer). If it is empty, the row is generated from the prompt only, as a 5 s 540x960 clip.",
        "width": 380,
        "height": 200
      }
    },
    {
      "id": "790947d6-af18-40cf-8107-88ca99979987",
      "name": "Run Manually",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -1000,
        100
      ],
      "parameters": {}
    },
    {
      "id": "31df12d6-607b-42ac-93b2-db5b618c966f",
      "name": "On New Sheet Row",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "typeVersion": 1,
      "position": [
        -1000,
        300
      ],
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0"
        },
        "event": "rowAdded",
        "options": {}
      },
      "credentials": {
        "googleSheetsTriggerOAuth2Api": {
          "id": "",
          "name": "Google Sheets Trigger account"
        }
      }
    },
    {
      "id": "96ea1577-83a2-4d48-ba4f-9dcdf9f88dbf",
      "name": "Read Pending Rows",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        -780,
        200
      ],
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "status",
              "lookupValue": ""
            }
          ]
        },
        "options": {}
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "",
          "name": "Google Sheets account"
        }
      },
      "alwaysOutputData": false
    },
    {
      "id": "57bb24a8-6d95-42c2-8b88-7bbf8c040e84",
      "name": "Loop Over Rows",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        -560,
        200
      ],
      "parameters": {
        "options": {}
      }
    },
    {
      "id": "a8caf525-05e5-474f-99de-2fc096a33195",
      "name": "Throttle Between Rows",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -340,
        300
      ],
      "parameters": {
        "amount": 5
      },
      "webhookId": "0149fc03-262b-4a11-b46f-50cb46854081"
    },
    {
      "id": "9b86a6db-baa5-4a25-b127-a110034bae6f",
      "name": "Write Video Prompt",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.5,
      "position": [
        -120,
        300
      ],
      "parameters": {
        "promptType": "define",
        "text": "=Write one cinematic prompt (max 60 words) for a 5 second vertical product video.\nDescribe camera movement, lighting and the product in motion. No text overlays, no people's faces, no brand claims.\nReturn only the prompt.\n\nProduct description: {{ $('Loop Over Rows').item.json.description }}"
      }
    },
    {
      "id": "0921e363-7b03-494d-a881-5407c2ce4d80",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        -120,
        500
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "id": "",
          "name": "OpenAI account"
        }
      }
    },
    {
      "id": "fe31b6b5-b6d8-4ae2-89d5-f1037801c1f1",
      "name": "Build Sisif Request",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        160,
        300
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "32a8318e-f8cf-417d-83d8-32b6b27c746a",
              "name": "prompt",
              "value": "={{ $json.text.trim() }}",
              "type": "string"
            },
            {
              "id": "187869a0-303d-4e52-b5e2-14ceaa9772f1",
              "name": "image",
              "value": "={{ ($('Loop Over Rows').item.json.image_url || '').startsWith('https://') ? $('Loop Over Rows').item.json.image_url : '' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "4bfcc7bb-6398-45f5-b3e0-168d849efd95",
      "name": "Create Sisif Video",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        380,
        300
      ],
      "parameters": {
        "method": "POST",
        "url": "https://sisif.ai/api/videos/generate/",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "prompt",
              "value": "={{ $json.prompt }}"
            },
            {
              "name": "duration",
              "value": "5"
            },
            {
              "name": "resolution",
              "value": "540x960"
            },
            {
              "name": "image",
              "value": "={{ $json.image }}"
            }
          ]
        },
        "options": {}
      },
      "credentials": {
        "httpBearerAuth": {
          "id": "",
          "name": "Sisif API key"
        }
      }
    },
    {
      "id": "8bc3a603-2a91-4487-9fc8-61fcc7ba5c96",
      "name": "Wait Before Status Check",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        600,
        300
      ],
      "parameters": {
        "amount": 15
      },
      "webhookId": "b9b676b6-df95-4b1f-a750-afda04bd9086"
    },
    {
      "id": "1f47af9a-aaaf-441c-9fb2-6a2df796a07f",
      "name": "Check Video Status",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        820,
        300
      ],
      "parameters": {
        "url": "=https://sisif.ai/api/videos/{{ $json.poll_id }}/",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "options": {}
      },
      "credentials": {
        "httpBearerAuth": {
          "id": "",
          "name": "Sisif API key"
        }
      }
    },
    {
      "id": "0535c7f1-5502-4ae4-a828-ca5f08a37851",
      "name": "Is Video Ready?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1040,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "c19a1ebc-e2cf-45b8-9bfa-9aa4e3914be7",
              "leftValue": "={{ $json.status }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "READY"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "5b51ffe4-659f-4225-8078-f7e4a116fd88",
      "name": "Did Generation Fail?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1260,
        420
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "0453a592-0734-4304-b53a-5f2f2d515b25",
              "leftValue": "={{ $json.status }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "ERROR"
            },
            {
              "id": "c607b189-e121-4d0a-8cef-52d9c80c4d33",
              "leftValue": "={{ $json.status }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "REFUNDED"
            }
          ],
          "combinator": "or"
        },
        "options": {}
      }
    },
    {
      "id": "97e7c09a-ed02-48c8-8995-2a8e6a30ff61",
      "name": "Save Video Result",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1480,
        160
      ],
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "row_number": "={{ $('Loop Over Rows').item.json.row_number }}",
            "status": "done",
            "video_id": "={{ $json.id }}",
            "video_url": "={{ $json.video_url }}"
          },
          "matchingColumns": [
            "row_number"
          ],
          "schema": [
            {
              "id": "row_number",
              "displayName": "row_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "video_id",
              "displayName": "video_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "video_url",
              "displayName": "video_url",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "id": "aa5c6f5a-c122-4b28-b22a-16bb9cf607bf",
      "name": "Mark Row Failed",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1480,
        400
      ],
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "row_number": "={{ $('Loop Over Rows').item.json.row_number }}",
            "status": "=failed: {{ $json.error_message || ($json.attempt >= 40 ? 'timed out after 40 status checks' : $json.status) }}",
            "video_id": "={{ $json.poll_id }}"
          },
          "matchingColumns": [
            "row_number"
          ],
          "schema": [
            {
              "id": "row_number",
              "displayName": "row_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "video_id",
              "displayName": "video_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "id": "60401b84-2581-4143-bd0a-69aa9f51fc34",
      "name": "Start Poll Counter",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        490,
        480
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "80d0166c-c1f1-4610-89d5-a8d2aa018061",
              "name": "poll_id",
              "value": "={{ $json.id }}",
              "type": "string"
            },
            {
              "id": "12c2dfa5-6a10-425b-9d45-13b080bae2bf",
              "name": "attempt",
              "value": "0",
              "type": "number"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "e468778c-2793-4885-b830-8b5853091fb0",
      "name": "Track Poll Attempt",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        930,
        480
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "67cecdc5-f11d-45b7-bbee-51e08f4e7dc9",
              "name": "poll_id",
              "value": "={{ $('Wait Before Status Check').item.json.poll_id }}",
              "type": "string"
            },
            {
              "id": "519cdb5f-cf13-4b62-ae0f-496067f112fd",
              "name": "attempt",
              "value": "={{ $('Wait Before Status Check').item.json.attempt + 1 }}",
              "type": "number"
            }
          ]
        },
        "options": {},
        "includeOtherFields": true
      }
    },
    {
      "id": "d99286ae-92da-4fc7-a1a7-7ef04945c4d3",
      "name": "Under Max Attempts?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1150,
        700
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "35e8f32d-5ad6-4178-87d7-e69cfe5dc65a",
              "leftValue": "={{ $json.attempt }}",
              "operator": {
                "type": "number",
                "operation": "lt"
              },
              "rightValue": 40
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Run Manually": {
      "main": [
        [
          {
            "node": "Read Pending Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On New Sheet Row": {
      "main": [
        [
          {
            "node": "Read Pending Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Pending Rows": {
      "main": [
        [
          {
            "node": "Loop Over Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Throttle Between Rows": {
      "main": [
        [
          {
            "node": "Write Video Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write Video Prompt": {
      "main": [
        [
          {
            "node": "Build Sisif Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Sisif Request": {
      "main": [
        [
          {
            "node": "Create Sisif Video",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Sisif Video": {
      "main": [
        [
          {
            "node": "Start Poll Counter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Before Status Check": {
      "main": [
        [
          {
            "node": "Check Video Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Video Status": {
      "main": [
        [
          {
            "node": "Track Poll Attempt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Video Result": {
      "main": [
        [
          {
            "node": "Loop Over Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Row Failed": {
      "main": [
        [
          {
            "node": "Loop Over Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Rows": {
      "main": [
        [],
        [
          {
            "node": "Throttle Between Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Video Ready?": {
      "main": [
        [
          {
            "node": "Save Video Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Did Generation Fail?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Did Generation Fail?": {
      "main": [
        [
          {
            "node": "Mark Row Failed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Under Max Attempts?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Write Video Prompt",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Start Poll Counter": {
      "main": [
        [
          {
            "node": "Wait Before Status Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Track Poll Attempt": {
      "main": [
        [
          {
            "node": "Is Video Ready?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Under Max Attempts?": {
      "main": [
        [
          {
            "node": "Wait Before Status Check",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark Row Failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  }
}