{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "c1ac5764-5a77-4105-bc2c-224badb11721",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -224,
        -240
      ],
      "parameters": {
        "width": 580,
        "height": 660,
        "content": "### Analyze Zoom phone recordings with Gemini AI\nThis workflow grabs your Zoom Phone call recordings, sends them to Gemini for analysis, backs them up to Google Drive, and logs everything in a spreadsheet.\n\n### How it works\n1. **Schedule Trigger** kicks off every few hours and pulls recent recordings from Zoom Phone API\n2. Each recording is downloaded, then sent to both **Google Drive** (for backup) and **Gemini AI** (for analysis)\n3. Gemini evaluates the call \u2014 how far the conversation got, and what could be improved\n4. Results are merged with the Drive link and appended to a **Google Sheets** log\n\n### Setup steps\n1. Create a Zoom Server-to-Server OAuth app with `phone:read` and `recording:read` scopes\n2. Add your Google Gemini API key in n8n credentials\n3. Connect Google Drive and Google Sheets OAuth2\n4. Set the **Google Drive folder ID** in the \"Upload to Google Drive\" node\n5. Set the **Google Sheets document ID** in the \"Log to Google Sheets\" node \u2014 make sure the sheet has columns: Caller, Date, Number, Direction, Stage, Advice, Drive URL\n\n### Customization\n- Change the schedule interval in the trigger node\n- Edit the Gemini prompt to match your call scoring criteria\n- Add more columns to the spreadsheet for extra tracking"
      },
      "typeVersion": 1
    },
    {
      "id": "12116eca-f4b4-4c4e-bc0e-8469676ff2b0",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 460,
        "height": 650,
        "content": "## Fetch Zoom recordings\nPulls recent phone recordings from the Zoom API and splits them into individual items for processing."
      },
      "typeVersion": 1
    },
    {
      "id": "0ec1e3ce-7526-499d-b07f-4dcc7675fb45",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        848,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 786,
        "height": 650,
        "content": "## Prepare & download\nBuilds a clean filename from the caller name + timestamp, then downloads the actual audio file."
      },
      "typeVersion": 1
    },
    {
      "id": "fbd4def3-d02a-48fc-bffb-eda5c1f9fad6",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1648,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 582,
        "height": 650,
        "content": "## Analyze with Gemini AI\nThe audio goes to both Google Drive (backup) and Gemini (analysis) in parallel. Gemini returns a structured JSON with the call stage and improvement advice."
      },
      "typeVersion": 1
    },
    {
      "id": "fd90ece7-4eb1-4122-b062-590d93c76a77",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2256,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 360,
        "height": 650,
        "content": "## Log results\nCombines the Gemini analysis with caller metadata and the Drive link, then appends a row to your tracking spreadsheet."
      },
      "typeVersion": 1
    },
    {
      "id": "e064d2f3-25c8-4985-bf3d-8b3316f78440",
      "name": "Get Zoom Phone Recordings",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        672,
        -128
      ],
      "parameters": {
        "url": "https://api.zoom.us/v2/phone/recordings",
        "options": {},
        "sendQuery": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "page_size",
              "value": "20"
            }
          ]
        },
        "nodeCredentialType": "zoomOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "570be236-0816-4287-81b4-5ad3cb1c6709",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1104,
        -128
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "b854e9de-3ca5-457a-a279-2205f3ad23cf",
      "name": "Build File Name",
      "type": "n8n-nodes-base.set",
      "position": [
        1328,
        -48
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a6650e12-8a03-4efe-9fbb-aec55950b140",
              "name": "FileName",
              "type": "string",
              "value": "={{ $json.caller_name }}_{{ $json.date_time.replace(/[-T:Z]/g, '') }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "941bbcab-c18b-4856-aa4f-4ad5f961ef37",
      "name": "Download Recording",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1520,
        -48
      ],
      "parameters": {
        "url": "={{ $('Loop Over Items').item.json.download_url }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "zoomOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "bb98c913-9b85-4b88-b47f-482c9e6e0e34",
      "name": "Upload to Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1712,
        -48
      ],
      "parameters": {
        "name": "={{ $('Build File Name').item.json.FileName }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": ""
        }
      },
      "typeVersion": 3
    },
    {
      "id": "bab6320f-cb6a-426d-9b3e-3e906cdbd79f",
      "name": "Upload Audio to Gemini",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1728,
        208
      ],
      "parameters": {
        "url": "https://generativelanguage.googleapis.com/upload/v1beta/files",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "binaryData",
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Goog-Upload-Command",
              "value": "upload, finalize"
            },
            {
              "name": "X-Goog-Upload-Protocol",
              "value": "raw"
            }
          ]
        },
        "inputDataFieldName": "data",
        "nodeCredentialType": "googlePalmApi"
      },
      "typeVersion": 4.4
    },
    {
      "id": "eb7f11da-47a1-491c-bf05-63a44f9936cb",
      "name": "Analyze Call with Gemini",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1920,
        208
      ],
      "parameters": {
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"contents\": [\n    {\n      \"parts\": [\n        {\n          \"fileData\": {\n            \"mimeType\": \"{{ $json.file.mimeType }}\",\n            \"fileUri\": \"{{ $json.file.uri }}\"\n          }\n        },\n        {\n          \"text\": \"Analyze this phone call recording and evaluate it as a sales/telemarketing call.\\n\\nDetermine:\\n- How far the call got (one of: Contact reached / Needs identified / Meeting proposed / Appointment booked)\\n- Specific, actionable advice for the caller\\n\\nReturn ONLY valid JSON, no other text.\"\n        }\n      ]\n    }\n  ],\n  \"generationConfig\": {\n    \"responseMimeType\": \"application/json\",\n    \"responseSchema\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"step\": {\n          \"type\": \"string\",\n          \"description\": \"Stage reached in the call (Contact reached / Needs identified / Meeting proposed / Appointment booked)\"\n        },\n        \"advice\": {\n          \"type\": \"string\",\n          \"description\": \"Specific advice for improving the call\"\n        }\n      },\n      \"required\": [\"step\", \"advice\"]\n    }\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googlePalmApi"
      },
      "typeVersion": 4.4
    },
    {
      "id": "79fa4644-956c-4207-8bb0-bc65b36c0dc4",
      "name": "Merge Drive Link + Analysis",
      "type": "n8n-nodes-base.merge",
      "position": [
        2096,
        192
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3.2
    },
    {
      "id": "4cc6f30f-4b58-4322-98ae-f78c50c1f166",
      "name": "Format Results",
      "type": "n8n-nodes-base.set",
      "position": [
        2288,
        192
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b1234567-+12345678900000001",
              "name": "Caller",
              "type": "string",
              "value": "={{ $('Loop Over Items').item.json.caller_name }}"
            },
            {
              "id": "b1234567-+12345678900000002",
              "name": "Date",
              "type": "string",
              "value": "={{ $('Loop Over Items').item.json.date_time }}"
            },
            {
              "id": "b1234567-+12345678900000003",
              "name": "Number",
              "type": "string",
              "value": "={{ $('Loop Over Items').item.json.callee_number }}"
            },
            {
              "id": "b1234567-+12345678900000004",
              "name": "Direction",
              "type": "string",
              "value": "={{ $('Loop Over Items').item.json.direction }}"
            },
            {
              "id": "b1234567-+12345678900000005",
              "name": "Stage",
              "type": "string",
              "value": "={{ JSON.parse($json.candidates[0].content.parts[0].text).step }}"
            },
            {
              "id": "b1234567-+12345678900000006",
              "name": "Advice",
              "type": "string",
              "value": "={{ JSON.parse($json.candidates[0].content.parts[0].text).advice }}"
            },
            {
              "id": "b1234567-+12345678900000007",
              "name": "DriveURL",
              "type": "string",
              "value": "={{ $json.webViewLink }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "0f60ec22-da9c-438b-a23f-4c26826e58a5",
      "name": "Log to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2464,
        192
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $json.Date }}",
            "Stage": "={{ $json.Stage }}",
            "Advice": "={{ $json.Advice }}",
            "Caller": "={{ $json.Caller }}",
            "Number": "={{ $json.Number }}",
            "DriveURL": "={{ $json.DriveURL }}",
            "Direction": "={{ $json.Direction }}"
          },
          "schema": [
            {
              "id": "Caller",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Caller",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Number",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Direction",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Direction",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Stage",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Stage",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Advice",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Advice",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "DriveURL",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "DriveURL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": ""
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "e68e2ece-e4d2-418b-9b69-446fd4f3ce96",
      "name": "Run Every Hour",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        448,
        -128
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "a5a8a6d8-a337-4c7b-bb9c-2a613fe5d3a6",
      "name": "Process Each Recording",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        880,
        -128
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "recordings"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Format Results": {
      "main": [
        [
          {
            "node": "Log to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run Every Hour": {
      "main": [
        [
          {
            "node": "Get Zoom Phone Recordings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build File Name": {
      "main": [
        [
          {
            "node": "Download Recording",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "Build File Name",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Recording": {
      "main": [
        [
          {
            "node": "Upload to Google Drive",
            "type": "main",
            "index": 0
          },
          {
            "node": "Upload Audio to Gemini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Google Sheets": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Each Recording": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Audio to Gemini": {
      "main": [
        [
          {
            "node": "Analyze Call with Gemini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload to Google Drive": {
      "main": [
        [
          {
            "node": "Merge Drive Link + Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Call with Gemini": {
      "main": [
        [
          {
            "node": "Merge Drive Link + Analysis",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Get Zoom Phone Recordings": {
      "main": [
        [
          {
            "node": "Process Each Recording",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Drive Link + Analysis": {
      "main": [
        [
          {
            "node": "Format Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}