{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "b2b02fc9-ea89-4d96-893f-f9e530876ea8",
      "name": "\ud83d\udccb Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -9264,
        -336
      ],
      "parameters": {
        "width": 620,
        "height": 584,
        "content": "## \ud83c\udfac 360\u00b0 Product Video Generator\n\nTurn a single product photo into a cinematic 360\u00b0 video using Google Veo 3 \u2014 delivered straight to Telegram.\n\n## How it works\n1. User sends a product photo to your Telegram bot\n2. The workflow authenticates with Google Cloud using a Service Account stored in Google Sheets\n3. The image is sent to Vertex AI (Veo 3) with a 360\u00b0 orbit camera prompt\n4. The workflow polls every 2 minutes until the video is ready (up to 10 min)\n5. The finished video is sent back to the user in Telegram\n\n## Setup steps\n1. Create a Telegram bot via @BotFather and add the credentials in n8n\n2. Enable the **Vertex AI API** in your Google Cloud project and request Veo 3 preview access\n3. Create a Service Account with `roles/aiplatform.user` and download the JSON key\n4. Paste the key fields into your Google Sheet \u2014 columns needed: `client_email`, `private_key`, `project_id`, `scope`\n5. Update the **1. Get Service Account Details** node with your Sheet ID\n6. Connect your Google and Telegram credentials, then activate the workflow"
      },
      "typeVersion": 1
    },
    {
      "id": "70a23ef7-b728-4388-b4e4-afe39ef8b657",
      "name": "Group 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -8528,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 428,
        "height": 348,
        "content": "## Receive & Validate\nListens for Telegram messages and checks that a photo was sent and is at least 480px. Rejects documents or text-only messages and replies with a clear error."
      },
      "typeVersion": 1
    },
    {
      "id": "0cf4efdf-44ac-4673-a88c-cb25452ca036",
      "name": "Group 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -8064,
        -176
      ],
      "parameters": {
        "color": 7,
        "width": 840,
        "height": 332,
        "content": "## Google Cloud Auth\nReads Service Account credentials from Google Sheets, signs a JWT locally, and exchanges it for a short-lived OAuth access token. Runs fresh on every request."
      },
      "typeVersion": 1
    },
    {
      "id": "db7f81ba-92c6-4677-a921-93e879d0771b",
      "name": "Group 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -7168,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 840,
        "height": 480,
        "content": "## Download & Convert\nDownloads the highest-resolution version of the photo from Telegram and converts it to Base64. Any conversion failure is caught and reported back to the user."
      },
      "typeVersion": 1
    },
    {
      "id": "a8977c41-5e0f-4e02-91ce-3fef4a6ca60b",
      "name": "Group 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -6272,
        -384
      ],
      "parameters": {
        "color": 7,
        "width": 620,
        "height": 508,
        "content": "## Submit to Veo 3\nBuilds the API payload with a cinematic 360\u00b0 orbit prompt and submits a long-running job to Vertex AI Veo 3. Gets back an operation ID used for polling."
      },
      "typeVersion": 1
    },
    {
      "id": "19948635-28b8-4c13-8ede-1a7bee91c42e",
      "name": "Group 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5616,
        -528
      ],
      "parameters": {
        "color": 7,
        "width": 1108,
        "height": 872,
        "content": "## Poll for Result\nChecks every 2 minutes whether the video is ready. Times out after 40 attempts (~10 min) and notifies the user with a friendly error if generation takes too long."
      },
      "typeVersion": 1
    },
    {
      "id": "be8ea122-84d1-4264-a646-be1010aa4bb2",
      "name": "Group 6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4496,
        -432
      ],
      "parameters": {
        "color": 7,
        "width": 660,
        "height": 276,
        "content": "## Deliver Video\nConverts the Base64 video bytes to a file and sends it directly to the user in Telegram."
      },
      "typeVersion": 1
    },
    {
      "id": "ee81c12f-2c54-441b-90a1-f5482c40b97e",
      "name": "Wait 2 Minutes",
      "type": "n8n-nodes-base.wait",
      "position": [
        -5552,
        -240
      ],
      "parameters": {
        "unit": "minutes",
        "amount": 2
      },
      "typeVersion": 1.1
    },
    {
      "id": "99b5b684-d316-4c79-9f58-d190be2e85df",
      "name": "Send Timeout Error",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -4784,
        144
      ],
      "parameters": {
        "text": "=\u274c Video generation failed after {{ $json.poll_count }} attempts.\n\nPlease try again with a different image or contact support.",
        "chatId": "={{ $json.chatId }}",
        "additionalFields": {
          "reply_to_message_id": "={{ $json.messageId }}"
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "e545feb6-efa8-4bf0-9bf0-0bb5d3fae486",
      "name": "Check Timeout",
      "type": "n8n-nodes-base.if",
      "position": [
        -5184,
        32
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.status }}",
              "value2": "timeout"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "7d7f7f2c-0e25-473b-bf87-7339957295d7",
      "name": "Download Image",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -6896,
        -160
      ],
      "parameters": {
        "fileId": "={{ $json.result.reply_to_message.photo[3].file_id }}",
        "resource": "file",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "83d5cd34-c7a8-454d-bea7-22854b9fea78",
      "name": "Telegram Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -8480,
        -48
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "1ed72edc-642e-4d2b-a33a-39eac6401c2c",
      "name": "2. Validate Input",
      "type": "n8n-nodes-base.code",
      "position": [
        -8256,
        -48
      ],
      "parameters": {
        "jsCode": "const inputData = $input.item.json;\nconst message = inputData.message;\nconst output = { ...inputData };\n\nif (!message) {\n  output.error = true;\n  output.errorMessage = '\u274c No message found.';\n  return [{ json: output }];\n}\n\nif (!message.photo || !Array.isArray(message.photo) || message.photo.length === 0) {\n  output.error = true;\n  output.errorMessage = '\u274c No photo found. Please send an image of your product.';\n  return [{ json: output }];\n}\n\nconst photo = message.photo[message.photo.length - 1];\nif (Math.min(photo.width, photo.height) < 480) {\n  output.error = true;\n  output.errorMessage = `\u274c Image too small (${photo.width}x${photo.height}).\\n\\nMinimum: 480px \u2014 Recommended: 1024x1024 or larger`;\n  return [{ json: output }];\n}\n\noutput.chatId = message.chat?.id;\noutput.messageId = message.message_id;\noutput.caption = message.caption || '';\noutput.file_id = photo.file_id;\noutput.error = false;\n\nreturn [{ json: output }];"
      },
      "typeVersion": 2
    },
    {
      "id": "1f36ad53-6c90-471e-b8a3-6686b96de5de",
      "name": "Check Auth Token Valid",
      "type": "n8n-nodes-base.if",
      "position": [
        -7344,
        -48
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "9e617a27-7ee6-44ba-a7cb-02b3e5738b08",
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              },
              "leftValue": "={{ $json.access_token.isEmpty() }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "3153c612-b11e-40ad-a24c-06b48b24f2eb",
      "name": "Send Validation Error",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -7120,
        32
      ],
      "parameters": {
        "text": "={{ $json.errorMessage }}",
        "chatId": "={{ $json.chatId }}",
        "additionalFields": {
          "reply_to_message_id": "={{ $json.messageId }}"
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "526add44-409e-41e6-b327-05c91a427708",
      "name": "Send Processing Message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -7120,
        -160
      ],
      "parameters": {
        "text": "\ud83c\udfac Creating your 360\u00b0 product video...\n\n\u23f1\ufe0f This takes around 3\u20134 minutes\n\ud83d\udd04 Processing with Google Veo 3...",
        "chatId": "={{ $json.chatId }}",
        "additionalFields": {
          "reply_to_message_id": "={{ $json.messageId }}"
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "238d0cb6-789f-4cbd-b894-38525e8b18e8",
      "name": "Convert Image to Base64",
      "type": "n8n-nodes-base.code",
      "position": [
        -6672,
        -160
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  try {\n    if (!item.binary || !item.binary.data) {\n      throw new Error('No binary data found');\n    }\n\n    let imageBuffer;\n    let mimeType = 'image/jpeg';\n\n    if (item.binary.data.data) {\n      imageBuffer = Buffer.from(item.binary.data.data, 'base64');\n      mimeType = item.binary.data.mimeType || mimeType;\n    } else if (Buffer.isBuffer(item.binary.data)) {\n      imageBuffer = item.binary.data;\n      mimeType = item.binary.mimeType || mimeType;\n    } else if (typeof item.binary.data === 'string') {\n      imageBuffer = Buffer.from(item.binary.data, 'base64');\n      mimeType = item.binary.mimeType || mimeType;\n    } else {\n      const binaryKeys = Object.keys(item.binary).filter(k =>\n        !['mimeType', 'fileType', 'fileName', 'fileExtension'].includes(k)\n      );\n      if (binaryKeys.length > 0) {\n        const binaryData = item.binary[binaryKeys[0]];\n        imageBuffer = Buffer.isBuffer(binaryData) ? binaryData\n          : binaryData.data ? Buffer.from(binaryData.data)\n          : Buffer.from(binaryData);\n        mimeType = item.binary.mimeType || mimeType;\n      } else {\n        throw new Error('Could not locate binary data');\n      }\n    }\n\n    if (!imageBuffer || imageBuffer.length === 0) throw new Error('Empty image buffer');\n\n    results.push({\n      json: {\n        ...item.json,\n        imageBase64: imageBuffer.toString('base64'),\n        imageMimeType: mimeType,\n        imageSize: imageBuffer.length,\n        conversionStatus: 'success'\n      }\n    });\n\n  } catch (error) {\n    results.push({\n      json: {\n        ...item.json,\n        error: true,\n        conversionStatus: 'failed',\n        conversionError: error.message\n      }\n    });\n  }\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "5696a1e9-ef12-4d7e-bc24-5cfcec661fec",
      "name": "Conversion OK?",
      "type": "n8n-nodes-base.if",
      "position": [
        -6448,
        -160
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.conversionStatus }}",
              "value2": "success"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f4ac9dea-d831-413b-9ecc-1181a8ee0185",
      "name": "Send Conversion Error",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -6016,
        -48
      ],
      "parameters": {
        "text": "=\u274c Failed to process your image: {{ $json.conversionError }}\n\nPlease try sending the photo again.",
        "chatId": "={{ $json.chatId }}",
        "additionalFields": {
          "reply_to_message_id": "={{ $json.messageId }}"
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "06685eae-cf1a-4469-84de-f2d06079121f",
      "name": "5. Prepare Veo Request",
      "type": "n8n-nodes-base.code",
      "position": [
        -6224,
        -240
      ],
      "parameters": {
        "jsCode": "const item = $input.item.json;\n\nconst basePrompt = `Create a professional 360-degree product showcase video. The camera smoothly orbits the product in a full 360-degree rotation, maintaining consistent framing. Use studio lighting with a clean white background. The product stays centered throughout. Smooth, cinematic movement \u2014 no jitter or cuts.`;\n\nconst userCaption = item.caption || '';\nconst finalPrompt = userCaption.trim().length > 0\n  ? basePrompt + ` Product context: ${userCaption.trim()}`\n  : basePrompt;\n\nconst apiPayload = {\n  instances: [{\n    prompt: finalPrompt,\n    image: {\n      bytesBase64Encoded: item.imageBase64,\n      mimeType: item.imageMimeType\n    }\n  }],\n  parameters: {\n    aspectRatio: '16:9',\n    sampleCount: 1,\n    durationSeconds: 8,\n    personGeneration: 'allow_all',\n    addWatermark: true,\n    includeRaiReason: true,\n    generateAudio: true\n  }\n};\n\nreturn {\n  api_payload: JSON.stringify(apiPayload),\n  chatId: item.chatId,\n  messageId: item.messageId,\n  caption: item.caption\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "010b2b1c-fdde-454d-851d-e8d1aa5b7bfb",
      "name": "6. Call Vertex AI Veo 3",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -6000,
        -240
      ],
      "parameters": {
        "url": "=https://us-central1-aiplatform.googleapis.com/v1/projects/{{ $('3. Get Access Token').item.json.project_id }}/locations/us-central1/publishers/google/models/veo-3.0-generate-preview:predictLongRunning",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ $json.api_payload }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('3. Get Access Token').item.json.access_token }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "1343ec01-9309-40c6-b287-df7d3af9941b",
      "name": "Extract Operation Name",
      "type": "n8n-nodes-base.code",
      "position": [
        -5776,
        -240
      ],
      "parameters": {
        "jsCode": "const response = $input.item.json;\n\nif (response.name) {\n  return [{\n    json: {\n      operation_name: response.name,\n      status: 'polling',\n      poll_count: 0,\n      chatId: response.chatId,\n      messageId: response.messageId,\n      caption: response.caption\n    }\n  }];\n} else {\n  return [{\n    json: {\n      ...response,\n      error: 'No operation name returned from Vertex AI',\n      status: 'failed'\n    }\n  }];\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "13ef8fde-d595-4fec-b48b-66e479cdd700",
      "name": "Poll Video Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -5328,
        -320
      ],
      "parameters": {
        "url": "=https://us-central1-aiplatform.googleapis.com/v1/projects/{{ $('3. Get Access Token').item.json.project_id }}/locations/us-central1/publishers/google/models/veo-3.0-generate-preview:fetchPredictOperation",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"operationName\": \"{{ $json.operation_name }}\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "041a8843-5462-4ff0-ae89-d7985463618a",
      "name": "Is Video Ready?",
      "type": "n8n-nodes-base.if",
      "position": [
        -5104,
        -320
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "5b5ff11d-4bb6-4955-9d99-b1e9e27666ea",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.done }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "4127d7d4-340d-4228-9ace-b287b6bb0526",
      "name": "Convert Video to File",
      "type": "n8n-nodes-base.convertToFile",
      "position": [
        -4880,
        -416
      ],
      "parameters": {
        "options": {},
        "operation": "toBinary",
        "sourceProperty": "response.videos[0].bytesBase64Encoded"
      },
      "typeVersion": 1.1
    },
    {
      "id": "82f5dcb0-62e4-43b7-9148-7bce5074361e",
      "name": "Continue Polling",
      "type": "n8n-nodes-base.code",
      "position": [
        -4880,
        -224
      ],
      "parameters": {
        "jsCode": "const item = $input.item.json;\nconst pollCount = (item.poll_count || 0) + 1;\n\nif (pollCount > 40) {\n  return {\n    error: 'Video generation timeout',\n    status: 'timeout',\n    poll_count: pollCount,\n    chatId: item.chatId,\n    messageId: item.messageId\n  };\n}\n\nreturn {\n  operation_name: item.name || item.operation_name,\n  poll_count: pollCount,\n  status: 'polling',\n  chatId: item.chatId,\n  messageId: item.messageId,\n  caption: item.caption\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "6b7f1886-adda-4211-a4ff-a0b16e4bb131",
      "name": "Send Video to User",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -4224,
        -320
      ],
      "parameters": {
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "operation": "sendVideo",
        "binaryData": true,
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "8fb2a208-a649-4182-83d1-87d715e946a2",
      "name": "2. Build JWT from Sheet",
      "type": "n8n-nodes-base.code",
      "position": [
        -7792,
        -48
      ],
      "parameters": {
        "jsCode": "const item = $input.item.json;\nconst crypto = require('crypto');\n\nconst SERVICE_ACCOUNT_EMAIL = item.client_email;\nconst PRIVATE_KEY = item.private_key.replace(/\\\\n/g, '\\n');\nconst PROJECT_ID = item.project_id;\nconst SCOPE = item.scope || 'https://www.googleapis.com/auth/cloud-platform';\nconst TOKEN_URI = 'https://oauth2.googleapis.com/token';\nconst now = Math.floor(Date.now() / 1000);\n\nfunction base64url(input) {\n  const buf = Buffer.isBuffer(input) ? input : Buffer.from(JSON.stringify(input));\n  return buf.toString('base64').replace(/=/g, '').replace(/\\+/g, '-').replace(/\\//g, '_');\n}\n\ntry {\n  const header = base64url({ alg: 'RS256', typ: 'JWT' });\n  const claims = base64url({ iss: SERVICE_ACCOUNT_EMAIL, scope: SCOPE, aud: TOKEN_URI, iat: now, exp: now + 3600 });\n  const signingInput = `${header}.${claims}`;\n  const sign = crypto.createSign('RSA-SHA256');\n  sign.update(signingInput);\n  sign.end();\n  const signature = sign.sign(PRIVATE_KEY).toString('base64').replace(/=/g, '').replace(/\\+/g, '-').replace(/\\//g, '_');\n\n  return [{ json: { ...item, jwt: `${signingInput}.${signature}`, project_id: PROJECT_ID, client_email: SERVICE_ACCOUNT_EMAIL, error: false } }];\n} catch (err) {\n  return [{ json: { ...item, error: true, errorMessage: `\u274c JWT Build Failed: ${err.message}` } }];\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "faa1f906-1f3a-4b08-8fc1-fe117ea31a51",
      "name": "3. Get Access Token",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -7568,
        -48
      ],
      "parameters": {
        "url": "https://oauth2.googleapis.com/token",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "form-urlencoded",
        "bodyParameters": {
          "parameters": [
            {
              "name": "grant_type",
              "value": "urn:ietf:params:oauth:grant-type:jwt-bearer"
            },
            {
              "name": "assertion",
              "value": "={{ $json.jwt }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "563b037e-91b8-4348-a994-abfa27fba9ec",
      "name": "1. Get Service Account Details",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -8016,
        -48
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID_HERE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit",
          "cachedResultName": "Service Account Credentials"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    }
  ],
  "connections": {
    "Check Timeout": {
      "main": [
        [
          {
            "node": "Send Timeout Error",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait 2 Minutes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Conversion OK?": {
      "main": [
        [
          {
            "node": "5. Prepare Veo Request",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Conversion Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Image": {
      "main": [
        [
          {
            "node": "Convert Image to Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 2 Minutes": {
      "main": [
        [
          {
            "node": "Poll Video Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Video Ready?": {
      "main": [
        [
          {
            "node": "Convert Video to File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Continue Polling",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Continue Polling": {
      "main": [
        [
          {
            "node": "Check Timeout",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "2. Validate Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Validate Input": {
      "main": [
        [
          {
            "node": "1. Get Service Account Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poll Video Status": {
      "main": [
        [
          {
            "node": "Is Video Ready?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Get Access Token": {
      "main": [
        [
          {
            "node": "Check Auth Token Valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert Video to File": {
      "main": [
        [
          {
            "node": "Send Video to User",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. Prepare Veo Request": {
      "main": [
        [
          {
            "node": "6. Call Vertex AI Veo 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Auth Token Valid": {
      "main": [
        [
          {
            "node": "Send Processing Message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Validation Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Operation Name": {
      "main": [
        [
          {
            "node": "Wait 2 Minutes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Build JWT from Sheet": {
      "main": [
        [
          {
            "node": "3. Get Access Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. Call Vertex AI Veo 3": {
      "main": [
        [
          {
            "node": "Extract Operation Name",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert Image to Base64": {
      "main": [
        [
          {
            "node": "Conversion OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Processing Message": {
      "main": [
        [
          {
            "node": "Download Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Get Service Account Details": {
      "main": [
        [
          {
            "node": "2. Build JWT from Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}