This workflow follows the Google Calendar → 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 →
{
"name": "[TEMPLATE] Full Class -> Calendar Sync",
"nodes": [
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "a0ae355f-b75b-4ad6-b548-013f5242e1af",
"name": "api_base_url",
"value": "https://api.octivfitness.com",
"type": "string"
},
{
"id": "9b663fd1-a608-4127-8992-9bde7908cca0",
"name": "api_endpoint",
"value": "/api/class-dates",
"type": "string"
},
{
"id": "6195d8c9-ffae-4839-90b9-041abe155650",
"name": "tenant_id",
"value": "102938",
"type": "string"
},
{
"id": "47347b1f-60c0-4722-a40a-cd22c9809adb",
"name": "location_id",
"value": "2814",
"type": "string"
},
{
"id": "d1628036-eb80-4a4f-bfce-956c087b2734",
"name": "package_id",
"value": "44053",
"type": "string"
},
{
"id": "dd4d7f8d-5f43-4d3b-af25-d9bc4992d68c",
"name": "calendar_id",
"value": "VOTRE_CALENDAR_ID@group.calendar.google.com",
"type": "string"
},
{
"id": "a66fd9ff-548b-4b40-b4f9-cf724267812a",
"name": "timezone_offset",
"value": "+01:00",
"type": "string"
},
{
"id": "e323935a-78be-4d7f-ab98-176bfc159a1d",
"name": "full_class_keyword",
"value": "complet",
"type": "string"
},
{
"id": "627247ae-3f8b-44d0-881e-012f40876f4b",
"name": "days_ahead",
"value": "14",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-800,
220
],
"id": "3994b52e-7cdd-4265-906a-1f3845d90525",
"name": "Config A",
"notes": ">>> CONFIGUREZ ICI <<<\n\nModifiez les valeurs ci-dessus pour votre logiciel.\n- api_base_url : URL de base de l'API\n- api_endpoint : endpoint pour la liste des cours\n- tenant_id, location_id, package_id : IDs specifiques a votre logiciel\n- calendar_id : ID de votre Google Calendar\n- timezone_offset : Decalage horaire (ex: +01:00, +02:00)\n- full_class_keyword : Mot-cle pour identifier un cours plein\n- days_ahead : Nombre de jours a scanner",
"notesInFlow": true
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "71660df3-80c8-4a8a-9b4d-27ecea842f65",
"name": "api_base_url",
"value": "https://api.octivfitness.com",
"type": "string"
},
{
"id": "e293b461-6b17-4582-b840-9e8934a5364f",
"name": "api_endpoint",
"value": "/api/class-dates",
"type": "string"
},
{
"id": "8faaded8-c930-4c55-ab82-5c0d1b91644e",
"name": "tenant_id",
"value": "102938",
"type": "string"
},
{
"id": "5bb3d740-ce8b-44b5-8776-7bd3ac29de3b",
"name": "location_id",
"value": "2814",
"type": "string"
},
{
"id": "58cb185a-dd39-4769-992a-9be392335fab",
"name": "package_id",
"value": "44053",
"type": "string"
},
{
"id": "1887487d-17cb-4801-a3fe-01e40cd3a262",
"name": "calendar_id",
"value": "VOTRE_CALENDAR_ID@group.calendar.google.com",
"type": "string"
},
{
"id": "f0335db5-b338-4d27-819b-9804dffa5c47",
"name": "timezone_offset",
"value": "+01:00",
"type": "string"
},
{
"id": "9a3ff0c9-b18b-41d2-99ed-e68b4dd5ffb9",
"name": "full_class_keyword",
"value": "complet",
"type": "string"
},
{
"id": "6356da2a-dcac-4f70-b562-d3172e46bcb5",
"name": "days_ahead",
"value": "14",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-786,
460
],
"id": "b2c948ee-a987-4662-a091-44c470eb44a5",
"name": "Config B",
"notes": ">>> CONFIGUREZ ICI <<<\n\nModifiez les valeurs ci-dessus pour votre logiciel.\n- api_base_url : URL de base de l'API\n- api_endpoint : endpoint pour la liste des cours\n- tenant_id, location_id, package_id : IDs specifiques a votre logiciel\n- calendar_id : ID de votre Google Calendar\n- timezone_offset : Decalage horaire (ex: +01:00, +02:00)\n- full_class_keyword : Mot-cle pour identifier un cours plein\n- days_ahead : Nombre de jours a scanner",
"notesInFlow": true
},
{
"parameters": {
"method": "=GET",
"url": "={{ $('Config A').first().json.api_base_url }}{{ $('Config A').first().json.api_endpoint }}?filter[tenantId]={{ $('Config A').first().json.tenant_id }}&filter[locationId]={{ $('Config A').first().json.location_id }}&filter[between]={{ $now.format('yyyy-MM-dd') }},{{ $now.plus(Number($('Config A').first().json.days_ahead), 'days').format('yyyy-MM-dd') }}&filter[packageIds]={{ $('Config A').first().json.package_id }}&filter[isSession]=0&filter[classBookingsForLeadToken]=&filter[isVisibleInApp]=1&includeInstructors=true&perPage=-1",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-704,
336
],
"id": "e8529f28-d7f6-410e-913d-65af1b9edb83",
"name": "HTTP Request",
"notes": "A ADAPTER si vous n'utilisez PAS OctivFitness.\n\nCette requete recupere la liste des cours a venir.\nAdaptez l'URL et les parametres a votre API.\n\nFormat de sortie attendu par le Code node:\nTableau d'objets avec les champs:\n- id, name, date, start_time, end_time\n- bookings_count, limit (capacite)\n- instructor.name, class.location.name",
"notesInFlow": false
},
{
"parameters": {
"jsCode": "function buildDateTime(date, time, offset) {\n return `${date}T${time}${offset}`;\n}\n\nreturn items\n .map(item => {\n const c = item.json;\n\n // S\u00e9curit\u00e9\n if (!c.date || !c.start_time || !c.end_time) {\n return null;\n }\n\n const offset = $('Config A').first().json.timezone_offset;\n\n const start = buildDateTime(c.date, c.start_time, offset);\n const end = buildDateTime(c.date, c.end_time, offset);\n\n // Filtrage : cours complet\n if (c.bookings_count < c.limit) {\n return null;\n }\n\n return {\n json: {\n id: c.id,\n activity_name: c.name || \"Unknown activity\",\n description: c.description || null,\n\n start,\n end,\n\n booked: c.bookings_count,\n capacity: c.limit,\n\n instructor_id: c.instructor?.id || null,\n instructor_name: c.instructor?.name || \"Unknown\",\n\n location_name: c.class?.location?.name || null,\n is_free: c.class?.is_free ?? false\n }\n };\n })\n .filter(Boolean);\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-240,
336
],
"id": "f0e06871-e5b8-43d2-9804-a533cac41646",
"name": "Code"
},
{
"parameters": {
"resource": "calendar",
"calendar": {
"__rl": true,
"value": "={{ $('Config A').first().json.calendar_id }}",
"mode": "id"
},
"timeMin": "={{ $json.start }}",
"timeMax": "={{ $json.end }}",
"options": {
"outputFormat": "availability"
}
},
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
208,
0
],
"id": "e5290486-f13c-4620-8d19-1c25b4f1e5aa",
"name": "Get availability in a calendar"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
-16,
336
],
"id": "67533603-aafd-49a8-96bc-8c08c3f6509b",
"name": "Loop Over Items"
},
{
"parameters": {
"calendar": {
"__rl": true,
"value": "={{ $('Config A').first().json.calendar_id }}",
"mode": "id"
},
"start": "={{ $('Loop Over Items').item.json.start }}",
"end": "={{ $('Loop Over Items').item.json.end }}",
"additionalFields": {
"description": "=Cr\u00e9neau plein enregistr\u00e9 automatiquement. ID : {{ $('Loop Over Items').item.json.id }}",
"summary": "={{ $('Loop Over Items').item.json.activity_name }} - {{ $('Config A').first().json.full_class_keyword }} : {{ $('Loop Over Items').item.json.booked }}/{{ $('Loop Over Items').item.json.capacity }} places"
}
},
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
656,
0
],
"id": "a96c1280-9ee8-4c89-9e95-12e172ee9801",
"name": "Create an event"
},
{
"parameters": {
"operation": "delete",
"calendar": {
"__rl": true,
"value": "={{ $('Config A').first().json.calendar_id }}",
"mode": "id"
},
"eventId": "={{ $json.id }}",
"options": {}
},
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
1776,
288
],
"id": "60789ae0-24f2-4cd8-a365-27b5fdea0c8c",
"name": "Delete an event",
"onError": "continueErrorOutput"
},
{
"parameters": {
"operation": "getAll",
"calendar": {
"__rl": true,
"value": "={{ $('Config A').first().json.calendar_id }}",
"mode": "id"
},
"limit": 1,
"timeMin": "={{ $json.start_time.toDateTime().toISO() }}",
"timeMax": "={{ $json.start_time.toDateTime().plus(1 'hour').toISO() }}",
"options": {}
},
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
1552,
192
],
"id": "d0fccfd4-ec50-47e6-804a-a0cc1f419ee2",
"name": "Get many events",
"alwaysOutputData": true,
"onError": "continueRegularOutput"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "edd7d01b-5ce8-4089-b37c-87b4d07a48b8",
"leftValue": "={{ $json.bookings_count }}",
"rightValue": "={{ $json.limit }}",
"operator": {
"type": "number",
"operation": "gte"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1328,
192
],
"id": "db2e6049-5f03-4586-9210-4d3fccb5effe",
"name": "If booking >= booking capacity"
},
{
"parameters": {
"url": "={{ $('Config A').first().json.api_base_url }}{{ $('Config A').first().json.api_endpoint }}/{{ $('Loop Over Items').item.json.id }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1104,
192
],
"id": "0e752e33-05ba-40fa-a516-515f45a8cf0f",
"name": "Getting session info",
"notes": "A ADAPTER si vous n'utilisez PAS OctivFitness.\nRecupere les details d'un cours specifique par son ID.",
"notesInFlow": false
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 2
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-896,
336
],
"id": "850576c2-a48f-4b42-b44b-eec93f676db0",
"name": "Schedule Trigger"
},
{
"parameters": {
"operation": "getAll",
"calendar": {
"__rl": true,
"value": "={{ $('Config A').first().json.calendar_id }}",
"mode": "id"
},
"timeMax": "={{ $now.plus({ days: Number($('Config A').first().json.days_ahead) }) }}",
"options": {}
},
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
656,
192
],
"id": "7a422289-3868-4ec8-8a1e-1f1bb85f2df1",
"name": "Get many events1"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "31c601ff-14d5-49c2-ab63-1ee04d5e3523",
"leftValue": "={{ $json.summary }}",
"rightValue": "={{ $('Config A').first().json.full_class_keyword }}",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "b25e8ca6-55ce-4a3a-82fa-aac0a2226cf4",
"leftValue": "",
"rightValue": "",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
880,
192
],
"id": "624264fe-5544-4404-bc30-c184cf703cf8",
"name": "Filter"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "1f33017a-97c5-4cf9-9901-993e2b04351e",
"leftValue": "={{ $json.available }}",
"rightValue": "true",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
432,
0
],
"id": "1a3a2349-5bcd-4b99-9518-af8bf7e732ae",
"name": "If event exists"
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-896,
560
],
"id": "f8c874c9-a261-4388-b317-44922784e97a",
"name": "Schedule Trigger1"
},
{
"parameters": {
"operation": "delete",
"calendar": {
"__rl": true,
"value": "={{ $('Config B').first().json.calendar_id }}",
"mode": "id"
},
"eventId": "={{ $json.id }}",
"options": {}
},
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
448,
560
],
"id": "2f453e59-4eb5-4db6-9652-51dd4635b846",
"name": "Delete an event1"
},
{
"parameters": {
"operation": "getAll",
"calendar": {
"__rl": true,
"value": "={{ $('Config B').first().json.calendar_id }}",
"mode": "id"
},
"limit": 1,
"timeMin": "={{ $json.start_time.toDateTime().toISO() }}",
"timeMax": "={{ $json.start_time.toDateTime().plus(1 'hour').toISO() }}",
"options": {}
},
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
224,
560
],
"id": "a40e78d2-7636-4405-bd11-04d23335a08f",
"name": "Get many events2"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "edd7d01b-5ce8-4089-b37c-87b4d07a48b8",
"leftValue": "={{ $json.bookings_count }}",
"rightValue": "={{ $json.limit }}",
"operator": {
"type": "number",
"operation": "gte"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
0,
560
],
"id": "2c03d091-3f9a-45c3-829b-e2e7717e7a63",
"name": "If booking >= booking capacity1"
},
{
"parameters": {
"url": "={{ $('Config B').first().json.api_base_url }}{{ $('Config B').first().json.api_endpoint }}/{{ $json.description.split('ID : ')[1] }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-224,
560
],
"id": "e397207b-2de6-4324-b418-a3f9d6b83083",
"name": "Getting session info1",
"notes": "A ADAPTER si vous n'utilisez PAS OctivFitness.\nRe-verifie si un cours est encore complet (Flux B).\n\nIMPORTANT: l'ID du cours est extrait de la description\nde l'evenement Calendar via split('ID : '). Si vous\nmodifiez le format de description dans 'Create an event',\nadaptez aussi cette extraction.",
"notesInFlow": false
},
{
"parameters": {
"operation": "getAll",
"calendar": {
"__rl": true,
"value": "={{ $('Config B').first().json.calendar_id }}",
"mode": "id"
},
"timeMax": "={{ $now.plus({ days: Number($('Config B').first().json.days_ahead) }) }}",
"options": {}
},
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
-672,
560
],
"id": "10b11288-6758-486e-a185-2fc2b1ba51a8",
"name": "Get many events3"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "31c601ff-14d5-49c2-ab63-1ee04d5e3523",
"leftValue": "={{ $json.summary }}",
"rightValue": "={{ $('Config B').first().json.full_class_keyword }}",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "b25e8ca6-55ce-4a3a-82fa-aac0a2226cf4",
"leftValue": "",
"rightValue": "",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
-448,
560
],
"id": "3eddbc8c-a052-41e7-8849-8bcdd4de5715",
"name": "Filter1"
},
{
"parameters": {
"fieldToSplitOut": "data",
"options": {}
},
"type": "n8n-nodes-base.splitOut",
"typeVersion": 1,
"position": [
-496,
336
],
"id": "3e775e4e-ebf3-4ed0-919d-e08e6ce32e4f",
"name": "Split Out1"
},
{
"parameters": {
"content": "## [TEMPLATE] Full Class -> Calendar Sync\n\n### Comment utiliser ce template :\n\n1. **Dupliquez** ce workflow (ne modifiez pas l'original)\n2. **Configurez** les nodes **Config A** et **Config B** avec vos propres valeurs\n - Les deux nodes doivent avoir les MEMES valeurs\n3. **Adaptez** les 3 nodes HTTP Request (orange) si votre logiciel n'est pas OctivFitness\n - Voir les notes sur chaque node pour le format attendu\n4. **Connectez** votre credential Google Calendar sur tous les nodes Calendar (bleus)\n5. **Testez** manuellement puis **activez**\n\n---\n\n### Flux A (toutes les 2h) :\nRecupere les cours -> detecte les cours complets -> cree un evenement Calendar\n\n### Flux B (toutes les 1h) :\nVerifie les evenements existants -> supprime ceux dont le cours n'est plus complet\n\n---\n\n### Format de donnees requis :\nLe **Code** node attend ces champs dans la reponse API :\n- `id` : identifiant unique du cours\n- `name` : nom de l'activite\n- `date` : date (YYYY-MM-DD)\n- `start_time`, `end_time` : heures (HH:MM:SS)\n- `bookings_count` : nombre de reservations\n- `limit` : capacite max du cours\n- `instructor.name` : nom de l'instructeur\n\n### Logiciels compatibles :\n- **OctivFitness** : Pret a l'emploi (config par defaut)\n- **MindBody** : Adapter les 3 HTTP Request nodes\n- **Wodify** : Adapter les 3 HTTP Request nodes\n- **Autre** : Tant que l'API renvoie les champs ci-dessus",
"width": 560,
"height": 620,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1300,
-200
],
"id": "9c1b4cf6-fa4b-44e1-b79d-5a063bb3a7a4",
"name": "Instructions"
}
],
"connections": {
"HTTP Request": {
"main": [
[
{
"node": "Split Out1",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Get availability in a calendar": {
"main": [
[
{
"node": "If event exists",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Get availability in a calendar",
"type": "main",
"index": 0
}
]
]
},
"Create an event": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Delete an event": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Get many events": {
"main": [
[
{
"node": "Delete an event",
"type": "main",
"index": 0
}
]
]
},
"If booking >= booking capacity": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
],
[
{
"node": "Get many events",
"type": "main",
"index": 0
}
]
]
},
"Getting session info": {
"main": [
[
{
"node": "If booking >= booking capacity",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Config A",
"type": "main",
"index": 0
}
]
]
},
"Get many events1": {
"main": [
[
{
"node": "Filter",
"type": "main",
"index": 0
}
]
]
},
"Filter": {
"main": [
[
{
"node": "Getting session info",
"type": "main",
"index": 0
}
]
]
},
"If event exists": {
"main": [
[
{
"node": "Create an event",
"type": "main",
"index": 0
}
],
[
{
"node": "Get many events1",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger1": {
"main": [
[
{
"node": "Config B",
"type": "main",
"index": 0
}
]
]
},
"Get many events2": {
"main": [
[
{
"node": "Delete an event1",
"type": "main",
"index": 0
}
]
]
},
"If booking >= booking capacity1": {
"main": [
[],
[
{
"node": "Get many events2",
"type": "main",
"index": 0
}
]
]
},
"Getting session info1": {
"main": [
[
{
"node": "If booking >= booking capacity1",
"type": "main",
"index": 0
}
]
]
},
"Get many events3": {
"main": [
[
{
"node": "Filter1",
"type": "main",
"index": 0
}
]
]
},
"Filter1": {
"main": [
[
{
"node": "Getting session info1",
"type": "main",
"index": 0
}
]
]
},
"Split Out1": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Config A": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"Config B": {
"main": [
[
{
"node": "Get many events3",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"timezone": "Europe/Paris",
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
[TEMPLATE] Full Class -> Calendar Sync. Uses httpRequest, googleCalendar. Scheduled trigger; 24 nodes.
Source: https://gist.github.com/AnandaTom/8fb4e000fc38e8a4e9fdd4650b31c72b — 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.
Teams that track absences in Everhour and want a shared Google Calendar view for quick planning. Ideal for managers, HR/OPS, and teammates who need instant visibility into approved time off. Pulls app
🕌 How it works
This workflow automates the process of finding local events and adding them directly to your Google Calendar. It eliminates the need for manual event tracking by automatically scraping event informati
Import Forex Factory Calendar events into Google Calendar. Delete past Forex Factory Calendar events from Google Calendar. Get reminders for important economic data releases — especially High Impact n
Simple Calendar Sync. Uses googleCalendar, httpRequest. Scheduled trigger; 4 nodes.