AutomationFlowsAI & RAG › Gemini File Search Ingestion Engine V2 (read Binary Files)

Gemini File Search Ingestion Engine V2 (read Binary Files)

Gemini File Search Ingestion Engine v2. Uses readBinaryFiles, httpRequest. Event-driven trigger; 13 nodes.

Event trigger★★★★☆ complexity13 nodesRead Binary FilesHTTP Request
AI & RAG Trigger: Event Nodes: 13 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #gemini-ingestion-engine-v2 — we link there as the canonical source.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Gemini File Search Ingestion Engine v2",
  "nodes": [
    {
      "parameters": {},
      "id": "trigger-manual",
      "name": "Manual Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        240,
        200
      ],
      "notes": "For direct use: Click 'Test Workflow' to upload a document."
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "gemini-ingestion",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "trigger-webhook",
      "name": "Webhook Start",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        400
      ],
      "notes": "For automation: Called by Drive Watcher workflow.\nExpects: file_binary (base64), file_name, doc_type, api_key, store_id"
    },
    {
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "api-key",
              "name": "api_key",
              "value": "YOUR_GEMINI_API_KEY_HERE",
              "type": "string"
            },
            {
              "id": "store-id",
              "name": "store_id",
              "value": "fileSearchStores/YOUR_STORE_ID_HERE",
              "type": "string"
            },
            {
              "id": "file-path",
              "name": "file_path",
              "value": "/path/to/your/document.pdf",
              "type": "string"
            },
            {
              "id": "doc-type",
              "name": "doc_type",
              "value": "general",
              "type": "string"
            }
          ]
        }
      },
      "id": "set-config-manual",
      "name": "Your Settings (Manual)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        460,
        200
      ],
      "notes": "EDIT THIS NODE for manual uploads:\n\n1. api_key: Get from aistudio.google.dev\n2. store_id: Your Gemini File Search store ID\n3. file_path: Full path to your document\n4. doc_type: One of: policy, faq, procedure, product, or general"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Process webhook input and extract config\nconst input = $input.first().json;\n\n// Extract config from webhook body\nconst config = {\n  api_key: input.api_key || input.body?.api_key,\n  store_id: input.store_id || input.body?.store_id,\n  doc_type: input.doc_type || input.body?.doc_type || 'general',\n  file_name: input.file_name || input.body?.file_name || 'uploaded_document',\n  // Flag to indicate this came from webhook\n  source: 'webhook'\n};\n\n// Get binary data from webhook\nconst binaryData = input.file_binary || input.body?.file_binary;\nconst mimeType = input.mime_type || input.body?.mime_type || 'application/octet-stream';\n\nif (!binaryData) {\n  return [{\n    json: {\n      status: 'ERROR',\n      error_code: 'NO_FILE_DATA',\n      message: 'No file data received in webhook request.',\n      troubleshooting: [\n        'Ensure file_binary is included in the request body',\n        'File should be base64 encoded'\n      ]\n    }\n  }];\n}\n\nreturn [{\n  json: config,\n  binary: {\n    data: {\n      data: binaryData,\n      mimeType: mimeType,\n      fileName: config.file_name\n    }\n  }\n}];"
      },
      "id": "code-webhook-config",
      "name": "Process Webhook Input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        400
      ],
      "notes": "Extracts configuration and binary data from webhook request."
    },
    {
      "parameters": {
        "fileSelector": "={{ $json.file_path }}",
        "options": {}
      },
      "id": "read-file",
      "name": "Read Your Document",
      "type": "n8n-nodes-base.readBinaryFiles",
      "typeVersion": 1,
      "position": [
        680,
        200
      ],
      "notes": "Reads the file from your computer (manual mode only)."
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      },
      "id": "merge-inputs",
      "name": "Merge Inputs",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        900,
        300
      ],
      "notes": "Combines manual and webhook inputs into single flow."
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Gemini File Search Ingestion Engine v2\n// Supports both manual file path input AND webhook binary input\n\nconst items = $input.all();\n\n// Find the item with binary data and config\nlet binaryData = null;\nlet config = {};\n\nfor (const item of items) {\n  // Check for config from manual path\n  if (item.json.api_key && item.json.file_path && !item.json.source) {\n    config = {\n      api_key: item.json.api_key,\n      store_id: item.json.store_id,\n      doc_type: item.json.doc_type || 'general',\n      source: 'manual'\n    };\n  }\n  \n  // Check for config from webhook\n  if (item.json.source === 'webhook') {\n    config = {\n      api_key: item.json.api_key,\n      store_id: item.json.store_id,\n      doc_type: item.json.doc_type || 'general',\n      file_name: item.json.file_name,\n      source: 'webhook'\n    };\n  }\n  \n  // Check for binary data\n  if (item.binary && item.binary.data) {\n    binaryData = item.binary.data;\n  }\n}\n\nconst apiKey = config.api_key;\nconst storeId = config.store_id;\nconst docType = config.doc_type || 'general';\n\n// Get file from binary data\nif (!binaryData) {\n  return [{\n    json: {\n      status: 'ERROR',\n      error_code: 'NO_FILE',\n      message: 'No file data found. Check that your file path is correct.',\n      troubleshooting: [\n        'Verify the file exists at the specified path',\n        'Make sure the path is absolute (starts with /)',\n        'Check file permissions'\n      ]\n    }\n  }];\n}\n\nconst fileBuffer = Buffer.from(binaryData.data, 'base64');\nconst mimeType = binaryData.mimeType;\nconst fileName = config.file_name || binaryData.fileName || 'document';\nconst fileSize = fileBuffer.length;\n\n// Validate file type\nconst validTypes = {\n  'application/pdf': 'PDF',\n  'text/plain': 'TXT',\n  'text/markdown': 'Markdown',\n  'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'DOCX'\n};\n\nif (!validTypes[mimeType]) {\n  return [{\n    json: {\n      status: 'ERROR',\n      error_code: 'INVALID_FILE_TYPE',\n      message: `File type not supported: ${mimeType}`,\n      supported_types: 'PDF, TXT, MD, DOCX',\n      troubleshooting: [\n        'Convert your file to one of the supported formats',\n        'PDF is recommended for best results'\n      ]\n    }\n  }];\n}\n\n// Validate file size (100MB limit)\nconst maxSize = 100 * 1024 * 1024;\nif (fileSize > maxSize) {\n  return [{\n    json: {\n      status: 'ERROR',\n      error_code: 'FILE_TOO_LARGE',\n      message: `File is ${(fileSize / 1024 / 1024).toFixed(2)}MB. Maximum is 100MB.`,\n      troubleshooting: [\n        'Split the document into smaller files',\n        'Remove images or compress the PDF'\n      ]\n    }\n  }];\n}\n\n// Validate API key format\nif (!apiKey || apiKey === 'YOUR_GEMINI_API_KEY_HERE' || apiKey.length < 20) {\n  return [{\n    json: {\n      status: 'ERROR',\n      error_code: 'INVALID_API_KEY',\n      message: 'Please add your Gemini API key in the \"Your Settings\" node.',\n      troubleshooting: [\n        'Go to aistudio.google.dev to get your API key',\n        'Copy the full API key into the api_key field'\n      ]\n    }\n  }];\n}\n\n// Validate store ID format\nif (!storeId || !storeId.startsWith('fileSearchStores/') || storeId === 'fileSearchStores/YOUR_STORE_ID_HERE') {\n  return [{\n    json: {\n      status: 'ERROR',\n      error_code: 'INVALID_STORE_ID',\n      message: 'Please add your File Search Store ID in the \"Your Settings\" node.',\n      troubleshooting: [\n        'Store ID should start with \"fileSearchStores/\"',\n        'Create a store first using the setup instructions',\n        'Example format: fileSearchStores/abc123xyz'\n      ]\n    }\n  }];\n}\n\n// Prepare metadata\nconst uploadDate = new Date().toISOString().split('T')[0];\nconst metadata = [\n  { key: 'version', string_value: '1.0' },\n  { key: 'status', string_value: 'current' },\n  { key: 'doc_type', string_value: docType },\n  { key: 'upload_date', string_value: uploadDate },\n  { key: 'file_name', string_value: fileName }\n];\n\ntry {\n  // Step 1: Initiate resumable upload\n  const initUrl = `https://generativelanguage.googleapis.com/upload/v1beta/${storeId}:uploadToFileSearchStore?key=${apiKey}`;\n  \n  const initResponse = await fetch(initUrl, {\n    method: 'POST',\n    headers: {\n      'X-Goog-Upload-Protocol': 'resumable',\n      'X-Goog-Upload-Command': 'start',\n      'X-Goog-Upload-Header-Content-Length': String(fileSize),\n      'X-Goog-Upload-Header-Content-Type': mimeType,\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify({\n      file_search_store_file: {\n        display_name: fileName,\n        custom_metadata: metadata\n      }\n    })\n  });\n\n  if (!initResponse.ok) {\n    const errorText = await initResponse.text();\n    let errorCode = 'API_ERROR';\n    let message = `Gemini API error: ${errorText}`;\n    let troubleshooting = ['Check your API key is valid', 'Verify your store ID is correct'];\n    \n    if (initResponse.status === 401 || initResponse.status === 403) {\n      errorCode = 'AUTH_FAILURE';\n      message = 'Authentication failed. Your API key may be invalid or expired.';\n      troubleshooting = [\n        'Go to aistudio.google.dev and verify your API key',\n        'Generate a new API key if needed',\n        'Make sure the Gemini API is enabled for your project'\n      ];\n    } else if (initResponse.status === 404) {\n      errorCode = 'STORE_NOT_FOUND';\n      message = 'File Search Store not found. Check your store ID.';\n      troubleshooting = [\n        'Verify your store_id starts with \"fileSearchStores/\"',\n        'Make sure the store exists (create one if needed)',\n        'Check for typos in the store ID'\n      ];\n    } else if (initResponse.status === 429) {\n      errorCode = 'QUOTA_EXCEEDED';\n      message = 'Rate limit reached. Wait a moment and try again.';\n      troubleshooting = [\n        'Wait 60 seconds before retrying',\n        'Check your API quota in Google Cloud Console',\n        'Consider upgrading your plan if you hit limits frequently'\n      ];\n    }\n    \n    return [{\n      json: {\n        status: 'ERROR',\n        error_code: errorCode,\n        message: message,\n        http_status: initResponse.status,\n        troubleshooting: troubleshooting\n      }\n    }];\n  }\n\n  const uploadUrl = initResponse.headers.get('x-goog-upload-url');\n  if (!uploadUrl) {\n    return [{\n      json: {\n        status: 'ERROR',\n        error_code: 'NO_UPLOAD_URL',\n        message: 'Failed to get upload URL from Gemini API.',\n        troubleshooting: [\n          'This is unusual - try again in a few minutes',\n          'Check Gemini API status page for outages'\n        ]\n      }\n    }];\n  }\n\n  // Step 2: Upload file bytes\n  const uploadResponse = await fetch(uploadUrl, {\n    method: 'PUT',\n    headers: {\n      'Content-Length': String(fileSize),\n      'X-Goog-Upload-Offset': '0',\n      'X-Goog-Upload-Command': 'upload, finalize'\n    },\n    body: fileBuffer\n  });\n\n  if (!uploadResponse.ok) {\n    const errorText = await uploadResponse.text();\n    return [{\n      json: {\n        status: 'ERROR',\n        error_code: 'UPLOAD_FAILED',\n        message: `Upload failed: ${errorText}`,\n        http_status: uploadResponse.status,\n        troubleshooting: [\n          'Try with a smaller file',\n          'Check your internet connection',\n          'Try again in a few minutes'\n        ]\n      }\n    }];\n  }\n\n  const result = await uploadResponse.json();\n\n  // Return success with all relevant info\n  return [{\n    json: {\n      status: 'SUCCESS',\n      message: 'Document uploaded successfully!',\n      file_id: result.name,\n      file_name: fileName,\n      file_type: validTypes[mimeType],\n      mime_type: mimeType,\n      size_bytes: fileSize,\n      size_formatted: `${(fileSize / 1024).toFixed(1)} KB`,\n      store_id: storeId,\n      source: config.source || 'manual',\n      metadata: {\n        version: '1.0',\n        status: 'current',\n        doc_type: docType,\n        upload_date: uploadDate\n      },\n      // Pass to verification step\n      _verification: {\n        api_key: apiKey,\n        store_id: storeId,\n        file_id: result.name\n      }\n    }\n  }];\n\n} catch (error) {\n  return [{\n    json: {\n      status: 'ERROR',\n      error_code: 'UNEXPECTED_ERROR',\n      message: error.message || 'An unexpected error occurred',\n      troubleshooting: [\n        'Check your internet connection',\n        'Verify your API key and store ID',\n        'Try again in a few minutes',\n        'If the problem persists, check the N8N execution logs'\n      ]\n    }\n  }];\n}"
      },
      "id": "code-upload",
      "name": "Upload to Gemini",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        300
      ],
      "notes": "This node validates your file and uploads it to Gemini File Search.\n\nIt handles:\n- File type validation (PDF, TXT, MD, DOCX)\n- File size check (max 100MB)\n- API authentication\n- Resumable upload protocol\n- Error handling with clear messages\n\nSupports both manual (file path) and webhook (binary) inputs."
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "condition-success",
              "leftValue": "={{ $json.status }}",
              "rightValue": "SUCCESS",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        }
      },
      "id": "if-success",
      "name": "Upload OK?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1340,
        300
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://generativelanguage.googleapis.com/v1beta/{{ $json._verification.store_id }}/files?key={{ $json._verification.api_key }}",
        "options": {}
      },
      "id": "http-verify",
      "name": "Verify Upload",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1560,
        200
      ],
      "notes": "Queries the store to confirm the file was added successfully."
    },
    {
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "final-status",
              "name": "status",
              "value": "={{ $('Upload to Gemini').item.json.status }}",
              "type": "string"
            },
            {
              "id": "final-message",
              "name": "message",
              "value": "={{ $('Upload to Gemini').item.json.message }}",
              "type": "string"
            },
            {
              "id": "final-file-id",
              "name": "file_id",
              "value": "={{ $('Upload to Gemini').item.json.file_id }}",
              "type": "string"
            },
            {
              "id": "final-file-name",
              "name": "file_name",
              "value": "={{ $('Upload to Gemini').item.json.file_name }}",
              "type": "string"
            },
            {
              "id": "final-file-type",
              "name": "file_type",
              "value": "={{ $('Upload to Gemini').item.json.file_type }}",
              "type": "string"
            },
            {
              "id": "final-size",
              "name": "size",
              "value": "={{ $('Upload to Gemini').item.json.size_formatted }}",
              "type": "string"
            },
            {
              "id": "final-store",
              "name": "store_id",
              "value": "={{ $('Upload to Gemini').item.json.store_id }}",
              "type": "string"
            },
            {
              "id": "final-source",
              "name": "source",
              "value": "={{ $('Upload to Gemini').item.json.source }}",
              "type": "string"
            },
            {
              "id": "final-metadata",
              "name": "metadata",
              "value": "={{ $('Upload to Gemini').item.json.metadata }}",
              "type": "object"
            },
            {
              "id": "final-verified",
              "name": "verified",
              "value": "={{ $json.files ? $json.files.some(f => f.name === $('Upload to Gemini').item.json.file_id) : false }}",
              "type": "boolean"
            },
            {
              "id": "final-next",
              "name": "next_steps",
              "value": "Your document is now in your knowledge base! The Librarian agent can now search and retrieve it.",
              "type": "string"
            }
          ]
        }
      },
      "id": "set-success",
      "name": "Success Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1780,
        200
      ],
      "notes": "Formats the final success response with verification status."
    },
    {
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "error-status",
              "name": "status",
              "value": "={{ $json.status }}",
              "type": "string"
            },
            {
              "id": "error-code",
              "name": "error_code",
              "value": "={{ $json.error_code }}",
              "type": "string"
            },
            {
              "id": "error-message",
              "name": "message",
              "value": "={{ $json.message }}",
              "type": "string"
            },
            {
              "id": "error-troubleshooting",
              "name": "troubleshooting",
              "value": "={{ $json.troubleshooting }}",
              "type": "array"
            }
          ]
        }
      },
      "id": "set-error",
      "name": "Error Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1560,
        400
      ],
      "notes": "Formats the error response with troubleshooting guidance."
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": {}
      },
      "id": "webhook-response-success",
      "name": "Webhook Response (Success)",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2000,
        200
      ],
      "notes": "Returns success response to webhook caller."
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": {
          "responseCode": 400
        }
      },
      "id": "webhook-response-error",
      "name": "Webhook Response (Error)",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1780,
        400
      ],
      "notes": "Returns error response to webhook caller."
    }
  ],
  "connections": {
    "Manual Start": {
      "main": [
        [
          {
            "node": "Your Settings (Manual)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Start": {
      "main": [
        [
          {
            "node": "Process Webhook Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Your Settings (Manual)": {
      "main": [
        [
          {
            "node": "Read Your Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Webhook Input": {
      "main": [
        [
          {
            "node": "Merge Inputs",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Read Your Document": {
      "main": [
        [
          {
            "node": "Merge Inputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Inputs": {
      "main": [
        [
          {
            "node": "Upload to Gemini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload to Gemini": {
      "main": [
        [
          {
            "node": "Upload OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload OK?": {
      "main": [
        [
          {
            "node": "Verify Upload",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Upload": {
      "main": [
        [
          {
            "node": "Success Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Success Result": {
      "main": [
        [
          {
            "node": "Webhook Response (Success)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Result": {
      "main": [
        [
          {
            "node": "Webhook Response (Error)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "meta": {
    "templateId": "gemini-ingestion-engine-v2"
  },
  "versionId": "v2-2025-11-27",
  "tags": [
    {
      "name": "Gemini",
      "createdAt": "2025-11-27T00:00:00.000Z",
      "updatedAt": "2025-11-27T00:00:00.000Z"
    },
    {
      "name": "RAG",
      "createdAt": "2025-11-27T00:00:00.000Z",
      "updatedAt": "2025-11-27T00:00:00.000Z"
    },
    {
      "name": "Week-3",
      "createdAt": "2025-11-27T00:00:00.000Z",
      "updatedAt": "2025-11-27T00:00:00.000Z"
    }
  ]
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Gemini File Search Ingestion Engine v2. Uses readBinaryFiles, httpRequest. Event-driven trigger; 13 nodes.

Source: https://github.com/8Dvibes/mindvalley-ai-mastery-students/blob/main/workflows/gemini-ingestion-engine-v2-2025-11-27.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Gemini File Search Ingestion Engine v2. Uses readBinaryFiles, httpRequest. Event-driven trigger; 13 nodes.

Read Binary Files, HTTP Request
AI & RAG

This pipeline is the first part of "Hybrid Search with Qdrant & n8n, Legal AI"*. The second part, "Hybrid Search with Qdrant & n8n, Legal AI: Retrieval", covers retrieval and simple evaluation.*

N8N Nodes Qdrant, HTTP Request
AI & RAG

[1/3 - anomaly detection] [1/2 - KNN classification] Batch upload dataset to Qdrant (crops dataset). Uses manualTrigger, googleCloudStorage, httpRequest, stickyNote. Event-driven trigger; 25 nodes.

Google Cloud Storage, HTTP Request
AI & RAG

[1/3 - anomaly detection] [1/2 - KNN classification] Batch upload dataset to Qdrant (crops dataset). Uses manualTrigger, googleCloudStorage, httpRequest, stickyNote. Event-driven trigger; 25 nodes.

Google Cloud Storage, HTTP Request
AI & RAG

Workflows from the webinar "Build production-ready AI Agents with Qdrant and n8n".

Google Cloud Storage, HTTP Request