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 →
{
"name": "chatbot Sub-Workflow 1",
"nodes": [
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c0b6e779-0f7b-41f0-81f8-457f2b31ccfe",
"name": "response",
"type": "array",
"value": "={{ $json.freeTimeSlots.toJsonString() }}"
}
]
},
"options": {}
},
"id": "3253a6a9-b2c8-4208-a979-7ec33e219a9a",
"name": "varResponse",
"type": "n8n-nodes-base.set",
"position": [
1152,
224
],
"typeVersion": 3.4
},
{
"parameters": {
"jsCode": "// 1. Settings - Adjusted for Lahore Time (PKT)\nconst businessHoursStart = \"08:00:00+05:00\"; \nconst businessHoursEnd = \"17:30:00+05:00\"; \n\n// 2. Grab all incoming items\nconst inputData = $input.all().map(item => item.json); \n\nfunction getDateWithTime(dateString, time) {\n const datePart = new Date(dateString).toISOString().split(\"T\")[0];\n return new Date(`${datePart}T${time.replace('+05:00', '')}+05:00`);\n}\n\nfunction getDayOfWeek(dateString) {\n const daysOfWeek = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"];\n return daysOfWeek[new Date(dateString).getDay()];\n}\n\nfunction addDays(date, days) {\n const result = new Date(date);\n result.setDate(result.getDate() + days);\n return result;\n}\n\nfunction formatDate(date) {\n return date.toISOString().split('T')[0];\n}\n\n// 3. UPDATED LOGIC: Set the window to exactly 2 weeks starting TODAY\nconst today = new Date();\n// Normalize to midnight UTC to avoid partial day issues\nconst minDate = new Date(today.toISOString().split('T')[0]); \nconst maxDate = addDays(minDate, 13); // Today + 13 more days = 14 days total\n\nconst eventsByDate = {};\ninputData.forEach(event => {\n if (event.start && (event.start.dateTime || event.start.date)) {\n // Handle both timed events and all-day events for sorting\n const dateKey = new Date(event.start.dateTime || event.start.date).toISOString().split(\"T\")[0];\n \n if (!eventsByDate[dateKey]) eventsByDate[dateKey] = [];\n \n if (event.transparency !== \"transparent\" && event.start.dateTime) {\n eventsByDate[dateKey].push({\n start: new Date(event.start.dateTime),\n end: new Date(event.end.dateTime)\n });\n }\n }\n});\n\nconst freeTimeSlots = [];\n\n// Loop through exactly 14 days\nfor (let currentDate = new Date(minDate); currentDate <= maxDate; currentDate = addDays(currentDate, 1)) {\n const dateStr = formatDate(currentDate);\n const busyEvents = eventsByDate[dateStr] || [];\n const businessStart = getDateWithTime(dateStr, businessHoursStart);\n const businessEnd = getDateWithTime(dateStr, businessHoursEnd);\n \n // If no busy events, the whole business day is free\n if (busyEvents.length === 0) {\n freeTimeSlots.push({\n date: dateStr,\n dayOfWeek: getDayOfWeek(dateStr),\n freeStart: businessStart.toISOString(),\n freeEnd: businessEnd.toISOString()\n });\n continue;\n }\n \n busyEvents.sort((a, b) => a.start - b.start);\n \n // Check gap before first event\n if (busyEvents[0].start > businessStart) {\n freeTimeSlots.push({\n date: dateStr,\n dayOfWeek: getDayOfWeek(dateStr),\n freeStart: businessStart.toISOString(),\n freeEnd: busyEvents[0].start.toISOString()\n });\n }\n \n // Check gaps between events\n for (let i = 0; i < busyEvents.length - 1; i++) {\n if (busyEvents[i].end < busyEvents[i+1].start) {\n freeTimeSlots.push({\n date: dateStr,\n dayOfWeek: getDayOfWeek(dateStr),\n freeStart: busyEvents[i].end.toISOString(),\n freeEnd: busyEvents[i+1].start.toISOString()\n });\n }\n }\n \n // Check gap after last event\n if (busyEvents[busyEvents.length - 1].end < businessEnd) {\n freeTimeSlots.push({\n date: dateStr,\n dayOfWeek: getDayOfWeek(dateStr),\n freeStart: busyEvents[busyEvents.length - 1].end.toISOString(),\n freeEnd: businessEnd.toISOString()\n });\n }\n}\n\nreturn [{ json: { freeTimeSlots } }];"
},
"id": "eceb01d1-aad7-4cc9-9ca9-e736ed3d62b3",
"name": "freeTimeSlots",
"type": "n8n-nodes-base.code",
"position": [
928,
224
],
"typeVersion": 2
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.route }}",
"rightValue": "availability"
}
]
},
"renameOutput": true,
"outputKey": "availability"
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "52fd844b-cc8d-471f-a56a-40e119b66194",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.route }}",
"rightValue": "message"
}
]
},
"renameOutput": true,
"outputKey": "message"
}
]
},
"options": {}
},
"id": "73f1b356-8c4e-478a-bc14-bf0e8354b5a6",
"name": "Switch",
"type": "n8n-nodes-base.switch",
"position": [
480,
224
],
"typeVersion": 3.2
},
{
"parameters": {
"operation": "getAll",
"calendar": {
"__rl": true,
"value": "926802204220574fc5eed7f2875bcb56c94be37a2aab969ca011119f45af7ade@group.calendar.google.com",
"mode": "list",
"cachedResultName": "business automation"
},
"returnAll": true,
"timeMin": "={{ $now.plus(2, 'days') }}",
"timeMax": "={{ $now.plus({ week: 2 }) }}",
"options": {
"timeZone": {
"__rl": true,
"value": "Asia/Karachi",
"mode": "list",
"cachedResultName": "Asia/Karachi"
}
}
},
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
704,
224
],
"id": "8abadbb7-5e7f-4642-b414-2435c67b8d1a",
"name": "Get many events",
"alwaysOutputData": true,
"credentials": {
"googleCalendarOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"workflowInputs": {
"values": [
{
"name": "route"
},
{
"name": "Query"
}
]
}
},
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.1,
"position": [
256,
224
],
"id": "83550fad-c798-47ff-bd58-d02eb673aaef",
"name": "Execute workflow trigger"
}
],
"connections": {
"freeTimeSlots": {
"main": [
[
{
"node": "varResponse",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Get many events",
"type": "main",
"index": 0
}
],
[]
]
},
"Get many events": {
"main": [
[
{
"node": "freeTimeSlots",
"type": "main",
"index": 0
}
]
]
},
"Execute workflow trigger": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1",
"availableInMCP": false
},
"versionId": "864296fc-6d5f-427f-9f3a-975b455c477d",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "tDVpNczsb8rGCkgb",
"tags": []
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleCalendarOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
chatbot Sub-Workflow 1. Uses googleCalendar, executeWorkflowTrigger. Event-driven trigger; 5 nodes.
Source: https://github.com/ibnbilal/n8n-AI-appointment-scheduling-chatbot/blob/main/chatbot_Sub-Workflow_1.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.
Agendamiento. Uses n8n-nodes-evolution-api, redis, dataTable, executeWorkflowTrigger. Event-driven trigger; 60 nodes.
This n8n template implements an MCP (Model Context Protocol)-compliant module for managing Google Calendar events in a context-aware, conflict-free manner.
Prevent concurrent workflow runs using Redis. Uses executeWorkflowTrigger, manualTrigger, stickyNote, executeWorkflow. Event-driven trigger; 43 nodes.
This workflow sets a small "lock" value in Redis so that only one copy of a long job can run at the same time. If another trigger fires while the job is still busy, the workflow sees the lock, stops e
Reputation Engine — Site Refresh. Uses httpRequest, executeWorkflowTrigger. Event-driven trigger; 35 nodes.