This workflow follows the Execute Workflow Trigger → HTTP Request recipe pattern — see all workflows that pair these two integrations.
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 →
{
"id": "aicp-drive-sync",
"name": "Drive \u2192 workspace sync (scrubbed)",
"meta": {
"description": "Pulls changed files from a scope's Google Drive folder, runs them through the PII scrubber, and lands tokenized text in workspace/scopes/<chain>/ingest/. Attach your Google Drive OAuth2 credential to the two HTTP nodes. Set FOLDER_ID + scope in the Config node. Activate for the schedule + last-sync memory to work."
},
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 15
}
]
}
},
"id": "eeeeeeee-0000-0000-0000-000000000001",
"name": "Every 15 min",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
220,
300
]
},
{
"parameters": {
"jsCode": "// CONFIG: which Drive folder feeds which scope. One folder->one scope per\n// workflow; duplicate the workflow per mapping (keeps failures isolated).\nconst FOLDER_ID = $env.DRIVE_FOLDER_ID;\nif (!FOLDER_ID) { throw new Error('DRIVE_FOLDER_ID is not set in the n8n environment. Set it in .env and recreate the n8n container. Refusing to run against an unset Drive folder rather than querying a bogus id.'); }\nconst SCOPE = 'data-team';\n// Last-sync memory (persists on ACTIVE schedule runs; manual runs re-sync 7d).\nconst sd = $getWorkflowStaticData('global');\nconst since = sd.lastSync || new Date(Date.now() - 7*24*3600*1000).toISOString();\nreturn [{ json: {\n scope: SCOPE,\n q: `'${FOLDER_ID}' in parents and trashed=false and modifiedTime > '${since}'`,\n runStartedAt: new Date().toISOString()\n} }];"
},
"id": "eeeeeeee-0000-0000-0000-000000000002",
"name": "Config + last sync",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
420,
300
]
},
{
"parameters": {
"url": "https://www.googleapis.com/drive/v3/files",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleDriveOAuth2Api",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "q",
"value": "={{ $json.q }}"
},
{
"name": "fields",
"value": "files(id,name,mimeType,modifiedTime)"
},
{
"name": "pageSize",
"value": "100"
},
{
"name": "supportsAllDrives",
"value": "true"
},
{
"name": "includeItemsFromAllDrives",
"value": "true"
}
]
},
"options": {}
},
"id": "eeeeeeee-0000-0000-0000-000000000003",
"name": "List changed files",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
620,
300
]
},
{
"parameters": {
"jsCode": "// One item per file, with the right download strategy per mime type.\n// Google-native docs export as text; plain text/markdown/csv download raw.\n// Everything else (images, video, binaries) is skipped EXPLICITLY \u2014 no silent gaps.\nconst cfg = $('Config + last sync').first().json;\nconst files = $input.first().json.files || [];\nconst out = [], skipped = [];\nfor (const f of files) {\n if (f.mimeType === 'application/vnd.google-apps.document') {\n out.push({ json: { ...cfg, ...f, url: `https://www.googleapis.com/drive/v3/files/${f.id}/export`, exportMime: 'text/plain' } });\n } else if (/^text\\/|^application\\/(json|xml)/.test(f.mimeType)) {\n out.push({ json: { ...cfg, ...f, url: `https://www.googleapis.com/drive/v3/files/${f.id}`, exportMime: null } });\n } else {\n skipped.push(`${f.name} (${f.mimeType})`);\n }\n}\nif (!out.length) return [{ json: { ...cfg, empty: true, skipped } }];\nout[0].json.skipped = skipped;\nreturn out;"
},
"id": "eeeeeeee-0000-0000-0000-000000000004",
"name": "Plan downloads",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
820,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true
},
"conditions": [
{
"leftValue": "={{ $json.empty }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "notEquals"
}
}
]
},
"options": {}
},
"id": "eeeeeeee-0000-0000-0000-000000000005",
"name": "Has files?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1020,
300
]
},
{
"parameters": {
"url": "={{ $json.url }}",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleDriveOAuth2Api",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "mimeType",
"value": "={{ $json.exportMime }}"
},
{
"name": "alt",
"value": "={{ $json.exportMime ? undefined : 'media' }}"
},
{
"name": "supportsAllDrives",
"value": "true"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "text",
"outputPropertyName": "content"
}
}
}
},
"id": "eeeeeeee-0000-0000-0000-000000000006",
"name": "Download text",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1220,
200
]
},
{
"parameters": {
"method": "POST",
"url": "http://scrubber:8080/ingest",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "x-scrubber-token",
"value": "change-me-to-a-long-random-secret"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { \"scope\": $json.scope, \"filename\": $json.name, \"text\": $json.content } }}",
"options": {}
},
"id": "eeeeeeee-0000-0000-0000-000000000007",
"name": "Scrub + ingest",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1420,
200
]
},
{
"parameters": {
"jsCode": "// Persist the sync watermark and produce a summary (feed into Notify if desired).\nconst sd = $getWorkflowStaticData('global');\nconst cfg = $('Config + last sync').first().json;\nsd.lastSync = cfg.runStartedAt;\nconst items = $input.all().map(i => i.json);\nconst files = items.filter(i => i.written);\nconst findings = {};\nfor (const f of files) for (const [k,v] of Object.entries(f.findings||{})) findings[k]=(findings[k]||0)+v;\nreturn [{ json: {\n severity: 'info',\n title: `Drive sync: ${files.length} file(s) \u2192 scope ${cfg.scope}`,\n message: `Ingested (tokenized): ${files.map(f=>f.written).join(', ') || 'none'}\\nPII tokenized: ${JSON.stringify(findings)}`,\n source: 'drive-sync'\n} }];"
},
"id": "eeeeeeee-0000-0000-0000-000000000008",
"name": "Watermark + summary",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1620,
200
]
},
{
"parameters": {
"source": "database",
"workflowId": {
"__rl": true,
"value": "aicp-notify",
"mode": "id"
},
"options": {
"waitForSubWorkflow": true
},
"workflowInputs": {
"mappingMode": "autoMapInputData",
"value": {},
"matchingColumns": [],
"schema": [
{
"id": "severity",
"displayName": "severity",
"type": "string",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
},
{
"id": "title",
"displayName": "title",
"type": "string",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
},
{
"id": "message",
"displayName": "message",
"type": "string",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
},
{
"id": "source",
"displayName": "source",
"type": "string",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": true
}
},
"id": "n0t1fy00-0000-0000-0000-303362112884",
"name": "Notify",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
1840,
200
]
},
{
"id": "ondemand",
"name": "Run on demand",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.1,
"position": [
220,
460
],
"parameters": {
"inputSource": "passthrough"
}
}
],
"connections": {
"Every 15 min": {
"main": [
[
{
"node": "Config + last sync",
"type": "main",
"index": 0
}
]
]
},
"Config + last sync": {
"main": [
[
{
"node": "List changed files",
"type": "main",
"index": 0
}
]
]
},
"List changed files": {
"main": [
[
{
"node": "Plan downloads",
"type": "main",
"index": 0
}
]
]
},
"Plan downloads": {
"main": [
[
{
"node": "Has files?",
"type": "main",
"index": 0
}
]
]
},
"Has files?": {
"main": [
[
{
"node": "Download text",
"type": "main",
"index": 0
}
],
[]
]
},
"Download text": {
"main": [
[
{
"node": "Scrub + ingest",
"type": "main",
"index": 0
}
]
]
},
"Scrub + ingest": {
"main": [
[
{
"node": "Watermark + summary",
"type": "main",
"index": 0
}
]
]
},
"Watermark + summary": {
"main": [
[
{
"node": "Notify",
"type": "main",
"index": 0
}
]
]
},
"Run on demand": {
"main": [
[
{
"node": "Config + last sync",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"errorWorkflow": "aicp-error-trigger"
},
"active": false
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Drive → workspace sync (scrubbed). Uses httpRequest, executeWorkflowTrigger. Scheduled trigger; 10 nodes.
Source: https://github.com/jgobuilds/ai-control-plane-public/blob/main/n8n-workflows/drive-sync.workflow.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
PF WF-02 Caption Generator v18.3. Uses microsoftSharePoint, httpRequest, executeWorkflowTrigger. Scheduled trigger; 39 nodes.
Deliverables → Drive (rehydrated). Uses httpRequest, executeWorkflowTrigger. Scheduled trigger; 15 nodes.
dispatch. Uses executeWorkflowTrigger, httpRequest. Scheduled trigger; 8 nodes.
Daily digest lane (scheduled, human-awareness digest tier). Uses httpRequest, executeWorkflowTrigger. Scheduled trigger; 6 nodes.
Dependency bumps (scheduled lane, AG-3). Uses httpRequest, executeWorkflowTrigger. Scheduled trigger; 6 nodes.