{
  "name": "Lumeor | AI meeting notes to HubSpot (Calendar + Zoom)",
  "nodes": [
    {
      "parameters": {
        "content": "## AI meeting notes to HubSpot (Google Calendar + Zoom + Slack)\n\n**What it does**\n1. Every 15 minutes, checks your Google Calendar for meetings that ended 30 minutes to 6 hours ago.\n2. Drops internal-only, private and short meetings before any AI step, and picks one external Zoom meeting per run.\n3. Fetches the Zoom transcript (retries until Zoom finishes processing) and matches attendees to HubSpot contacts by email.\n4. Claude writes a short summary, deal fields and next steps, each with the exact quote it came from. Code drops steps whose quote is not in the transcript.\n5. The rep gets a Slack preview with a \"Log to HubSpot\" button. Approve writes a note on the matched contacts. Ignore skips it.\n\n**Setup**\n1. Credentials: Google Calendar OAuth2, Zoom OAuth2 (with cloud recording read scope), HubSpot private app token, Anthropic, Slack.\n2. Edit the constants at the top of **Pick meeting** (your domains, rep email to Slack ID map, default reviewer).\n3. Replace REPLACE_WITH_SLACK_CHANNEL in **Post notes issue**.\n4. Activate the workflow. Workflow static data (used for de-duplication) only persists in active runs.\n\nModel: claude-opus-5-5. claude-haiku-5-5 is a cheaper option for short meetings.\n\nTemplate by Lumeor Studio, https://www.lumeor.studio/guides/ai-meeting-notes-to-crm/",
        "height": 720,
        "width": 520
      },
      "id": "3bf5efa2-f85e-4188-b0ee-dbcdbe953aaa",
      "name": "Template notes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -620,
        -260
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "*/15 * * * *"
            }
          ]
        }
      },
      "id": "315ab369-ca9d-4844-b2cf-ce462460b9f7",
      "name": "Every 15 minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://www.googleapis.com/calendar/v3/calendars/primary/events",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleCalendarOAuth2Api",
        "sendQuery": true,
        "specifyQuery": "keypair",
        "queryParameters": {
          "parameters": [
            {
              "name": "timeMin",
              "value": "={{ new Date(Date.now() - 8 * 3600000).toISOString() }}"
            },
            {
              "name": "timeMax",
              "value": "={{ new Date().toISOString() }}"
            },
            {
              "name": "singleEvents",
              "value": "true"
            },
            {
              "name": "orderBy",
              "value": "startTime"
            },
            {
              "name": "maxResults",
              "value": "100"
            }
          ]
        },
        "options": {}
      },
      "id": "e8076733-c683-4102-8561-ca88d8aedf97",
      "name": "Google Calendar: recent meetings",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Picks ONE finished external Zoom meeting per run (the Slack approval step waits on one item).\n// Internal-only, private and short meetings are dropped before any AI step.\nconst INTERNAL_DOMAINS = ['REPLACE_WITH_YOUR_DOMAIN.com'].map((d) => d.toLowerCase());\nconst OWNER_SLACK_IDS = { 'REPLACE_WITH_REP_EMAIL': 'REPLACE_WITH_REP_SLACK_USER_ID' };\nconst DEFAULT_REVIEWER_SLACK_ID = 'REPLACE_WITH_DEFAULT_REVIEWER_SLACK_USER_ID';\nconst MIN_MINUTES = 10;\nconst MAX_ATTEMPTS = 12; // about 3 hours of retries while Zoom finishes the transcript\n\nconst state = $getWorkflowStaticData('global');\nstate.meetings = state.meetings || {};\nconst now = Date.now();\nfor (const [k, v] of Object.entries(state.meetings)) if (now - v.seen > 3 * 86400000) delete state.meetings[k];\n\nconst domainOf = (e) => String(e || '').toLowerCase().split('@')[1] || '';\nconst zoomId = (ev) => {\n  const texts = [ev.location, ev.description, ...((ev.conferenceData && ev.conferenceData.entryPoints) || []).map((p) => p.uri)];\n  for (const t of texts) {\n    const m = String(t || '').match(/zoom\\.us\\/(?:j|my)\\/(\\d{9,12})/);\n    if (m) return m[1];\n  }\n  return '';\n};\n\nconst candidates = ($input.first().json.items || []).filter((ev) => {\n  if (ev.status === 'cancelled' || ['private', 'confidential'].includes(ev.visibility)) return false;\n  const start = Date.parse((ev.start || {}).dateTime);\n  const end = Date.parse((ev.end || {}).dateTime);\n  if (!start || !end || (end - start) / 60000 < MIN_MINUTES) return false;\n  if (end > now - 30 * 60000 || end < now - 6 * 3600000) return false; // give Zoom 30 minutes to process\n  const external = (ev.attendees || []).filter((a) => !a.resource && a.email && !INTERNAL_DOMAINS.includes(domainOf(a.email)));\n  if (!external.length || !zoomId(ev)) return false;\n  const s = state.meetings[ev.id];\n  return !(s && (s.done || s.attempts >= MAX_ATTEMPTS));\n}).sort((a, b) => Date.parse(a.end.dateTime) - Date.parse(b.end.dateTime));\n\nconst ev = candidates[0];\nif (!ev) return [];\nstate.meetings[ev.id] = state.meetings[ev.id] || { attempts: 0, seen: now };\nstate.meetings[ev.id].attempts++;\n\nconst owner = String((ev.organizer || {}).email || '').toLowerCase();\nconst attendees = (ev.attendees || []).filter((a) => !a.resource && a.email).map((a) => ({\n  email: a.email.toLowerCase(), name: a.displayName || '', external: !INTERNAL_DOMAINS.includes(domainOf(a.email)),\n}));\nreturn [{ json: {\n  eventId: ev.id,\n  title: ev.summary || 'Meeting',\n  start: ev.start.dateTime,\n  end: ev.end.dateTime,\n  zoomMeetingId: zoomId(ev),\n  owner,\n  reviewerSlackId: OWNER_SLACK_IDS[owner] || DEFAULT_REVIEWER_SLACK_ID,\n  attendees,\n  externalEmails: attendees.filter((a) => a.external).map((a) => a.email),\n}, pairedItem: { item: 0 } }];\n"
      },
      "id": "489a7cda-c118-4570-9748-64d559c182e6",
      "name": "Pick meeting",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.zoom.us/v2/meetings/{{ $json.zoomMeetingId }}/recordings",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "zoomOAuth2Api",
        "options": {}
      },
      "id": "268aa9d8-364e-426b-a94a-fea89a4f191f",
      "name": "Zoom: recording files",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        0
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Finds the finished transcript file. If Zoom has not produced it yet, stop and retry on the next run.\nconst meeting = $('Pick meeting').first().json;\nconst files = ($input.first().json.recording_files || []).filter((f) => f.file_type === 'TRANSCRIPT' && f.status === 'completed');\nif (!files.length) return [];\nconst state = $getWorkflowStaticData('global');\nstate.meetings[meeting.eventId].done = true; // one attempt at drafting per meeting\nreturn [{ json: { ...meeting, transcriptUrl: files[0].download_url }, pairedItem: { item: 0 } }];\n"
      },
      "id": "d7c7c942-1519-46a2-9355-e74a6b5f5b11",
      "name": "Pick transcript",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        0
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ $json.transcriptUrl }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "zoomOAuth2Api",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text",
              "outputPropertyName": "data"
            }
          },
          "timeout": 60000
        }
      },
      "id": "0040b037-8783-4b4d-be15-b0b707b44a20",
      "name": "Download transcript",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        0
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/contacts/search",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "hubspotAppToken",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ filterGroups: [{ filters: [{ propertyName: 'email', operator: 'IN', values: $('Pick transcript').first().json.externalEmails }] }], properties: ['email', 'firstname', 'lastname', 'company'], limit: 50 }) }}",
        "options": {}
      },
      "id": "3f11f947-7850-4273-95cb-9dac8b912003",
      "name": "HubSpot: match attendees",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1320,
        0
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Cleans the VTT transcript and asks Claude for a short summary, deal fields and quoted next steps.\nconst meeting = $('Pick transcript').first().json;\nconst vtt = String($('Download transcript').first().json.data || '');\nconst transcript = vtt.split('\\n')\n  .filter((l) => l.trim() && l.trim() !== 'WEBVTT' && !/^\\d+$/.test(l.trim()) && !/-->/.test(l))\n  .join('\\n').slice(0, 400000);\nconst contacts = ($input.first().json.results || []).map((c) => ({ id: c.id, email: c.properties.email, name: [c.properties.firstname, c.properties.lastname].filter(Boolean).join(' '), company: c.properties.company || '' }));\n\nconst schema = {\n  type: 'object', additionalProperties: false,\n  required: ['is_customer_conversation', 'summary_lines', 'deal_fields', 'next_steps'],\n  properties: {\n    is_customer_conversation: { type: 'boolean', description: 'False for interviews, vendor pitches or internal chats' },\n    summary_lines: { type: 'array', items: { type: 'string' }, description: 'Five to eight short lines' },\n    deal_fields: {\n      type: 'object', additionalProperties: false, required: ['pain', 'decision_process', 'timeline', 'competitors'],\n      properties: {\n        pain: { type: 'string' }, decision_process: { type: 'string' }, timeline: { type: 'string' }, competitors: { type: 'string' },\n      },\n    },\n    next_steps: {\n      type: 'array',\n      items: {\n        type: 'object', additionalProperties: false, required: ['owner_side', 'task', 'due_date', 'quote'],\n        properties: {\n          owner_side: { type: 'string', enum: ['us', 'them'] },\n          task: { type: 'string' },\n          due_date: { type: 'string', description: 'YYYY-MM-DD only if a date was said, else empty string' },\n          quote: { type: 'string', description: 'The exact sentence from the transcript this step comes from' },\n        },\n      },\n    },\n  },\n};\nconst prompt = 'Meeting: ' + meeting.title + ' on ' + meeting.start + '\\nAttendees: ' +\n  meeting.attendees.map((a) => (a.name || a.email) + (a.external ? ' (customer side)' : ' (our team)')).join(', ') +\n  '\\n\\nWrite CRM notes for this meeting. Use empty strings for deal fields that were not discussed. ' +\n  'Only include next steps someone actually agreed to, and copy the sentence they came from exactly.\\n\\nTranscript:\\n' + transcript;\nreturn [{ json: {\n  ...meeting, contacts, transcript,\n  claudeRequest: { model: 'claude-opus-5-5', max_tokens: 4000, messages: [{ role: 'user', content: prompt }], output_config: { format: { type: 'json_schema', schema } } },\n}, pairedItem: { item: 0 } }];\n"
      },
      "id": "ad08bbc4-abce-4d75-a6ad-af42813c065a",
      "name": "Build notes request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1540,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "anthropicApi",
        "sendHeaders": true,
        "specifyHeaders": "keypair",
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.claudeRequest) }}",
        "options": {
          "timeout": 180000
        }
      },
      "id": "5a93cf8e-e8f6-4d88-b794-3b68df8cee10",
      "name": "Claude: draft notes",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1760,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Checks every next step against the transcript and builds the Slack preview and the HubSpot note.\n\n// Reads a Messages API response. Thinking blocks may come first, so find the text block.\nfunction readClaude(resp) {\n  if (!resp || resp.error) {\n    const msg = resp && resp.error ? (resp.error.message || JSON.stringify(resp.error)) : 'empty response';\n    return { ok: false, reason: 'api_error: ' + String(msg).slice(0, 300) };\n  }\n  if (resp.stop_reason === 'refusal') {\n    const cat = resp.stop_details && resp.stop_details.category ? ' (' + resp.stop_details.category + ')' : '';\n    return { ok: false, reason: 'model_refusal' + cat };\n  }\n  if (resp.stop_reason === 'max_tokens') return { ok: false, reason: 'max_tokens: output was cut off, raise max_tokens' };\n  const block = (resp.content || []).find((b) => b.type === 'text');\n  if (!block) return { ok: false, reason: 'no_text_block_in_response' };\n  try {\n    return { ok: true, data: JSON.parse(block.text) };\n  } catch (e) {\n    return { ok: false, reason: 'invalid_json: ' + e.message };\n  }\n}\n\nconst m = $('Build notes request').first().json;\nconst r = readClaude($input.first().json);\nconst norm = (s) => String(s || '').toLowerCase().replace(/[^a-z0-9 ]/g, ' ').replace(/\\s+/g, ' ').trim();\nconst esc = (s) => String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\nconst when = String(m.start).slice(0, 10);\nif (!r.ok) {\n  return [{ json: { route: 'exception', slackText: ':warning: Could not draft notes for \"' + m.title + '\" (' + when + '): ' + r.reason }, pairedItem: { item: 0 } }];\n}\nconst d = r.data;\nif (!d.is_customer_conversation) return []; // not a customer meeting, nothing to log\nif (!m.contacts.length) {\n  return [{ json: { route: 'exception', slackText: ':bust_in_silhouette: Notes for \"' + m.title + '\" (' + when + ') were not logged: no HubSpot contact matches ' + m.externalEmails.join(', ') + '. Create the contacts and log the notes by hand.' }, pairedItem: { item: 0 } }];\n}\nconst t = norm(m.transcript);\nconst steps = d.next_steps.filter((s) => norm(s.quote).length >= 15 && t.includes(norm(s.quote)));\nconst dropped = d.next_steps.length - steps.length;\nconst fields = Object.entries(d.deal_fields).filter(([, v]) => v);\n\nconst noteBody = '<p><strong>' + esc(m.title) + '</strong> (' + when + ', from transcript, reviewed by the rep)</p><ul>' +\n  d.summary_lines.map((l) => '<li>' + esc(l) + '</li>').join('') + '</ul>' +\n  (fields.length ? '<p>' + fields.map(([k, v]) => '<strong>' + k.replace('_', ' ') + ':</strong> ' + esc(v)).join('<br>') + '</p>' : '') +\n  (steps.length ? '<p><strong>Next steps</strong></p><ul>' + steps.map((s) => '<li>' + (s.owner_side === 'us' ? 'Us: ' : 'Them: ') + esc(s.task) + (s.due_date ? ' (by ' + s.due_date + ')' : '') + '</li>').join('') + '</ul>' : '');\n\nconst preview = '*Notes ready for \"' + m.title + '\"* (' + when + ')\\nWill be logged on: ' + m.contacts.map((c) => c.name || c.email).join(', ') +\n  '\\n\\n' + d.summary_lines.map((l) => '- ' + l).join('\\n') +\n  (fields.length ? '\\n\\n' + fields.map(([k, v]) => '*' + k.replace('_', ' ') + ':* ' + v).join('\\n') : '') +\n  (steps.length ? '\\n\\n*Next steps*\\n' + steps.map((s) => '- ' + s.owner_side + ': ' + s.task + (s.due_date ? ' (' + s.due_date + ')' : '')).join('\\n') : '') +\n  (dropped ? '\\n_' + dropped + ' suggested step(s) dropped because the quote was not found in the transcript._' : '') +\n  '\\n\\nClick Log to HubSpot to save. Ignore this message to skip.';\n\nreturn [{ json: {\n  route: 'review', reviewerSlackId: m.reviewerSlackId, slackMessage: preview, noteBody,\n  timestamp: new Date(m.start).toISOString(), contactIds: m.contacts.map((c) => c.id),\n}, pairedItem: { item: 0 } }];\n"
      },
      "id": "f1d51c63-9cb0-41e4-a574-961da7782ec8",
      "name": "Parse notes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1980,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "29134f03-c80e-4a3d-895a-cc5a3fe9f487",
              "leftValue": "={{ $json.route }}",
              "rightValue": "review",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "084d756e-8c3d-4d26-a03d-4441da8233a3",
      "name": "Ready for review?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2200,
        0
      ]
    },
    {
      "parameters": {
        "authentication": "accessToken",
        "resource": "message",
        "operation": "sendAndWait",
        "select": "user",
        "user": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.reviewerSlackId }}"
        },
        "message": "={{ $json.slackMessage }}",
        "responseType": "approval",
        "approvalOptions": {
          "values": {
            "approvalType": "single",
            "approveLabel": "Log to HubSpot"
          }
        },
        "options": {
          "appendAttribution": false
        }
      },
      "id": "a6e5f5c3-d89e-4bc5-82c1-2424afb84663",
      "name": "Ask rep to approve",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        2420,
        -120
      ],
      "webhookId": "d76f7692-6d66-4077-b861-12432bab7980"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/notes",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "hubspotAppToken",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ properties: { hs_timestamp: $('Parse notes').first().json.timestamp, hs_note_body: $('Parse notes').first().json.noteBody }, associations: $('Parse notes').first().json.contactIds.map(id => ({ to: { id }, types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 202 }] })) }) }}",
        "options": {}
      },
      "id": "2fb37e26-6bb8-4bfe-8b14-daef99a4d6f8",
      "name": "HubSpot: create note",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2640,
        -120
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "authentication": "accessToken",
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "#REPLACE_WITH_SLACK_CHANNEL"
        },
        "messageType": "text",
        "text": "={{ $json.slackText || (':warning: Meeting notes workflow error: ' + (($json.error && ($json.error.message || $json.error.description)) || JSON.stringify($json.error || $json).slice(0, 500)) + ' (execution ' + $execution.id + ')') }}",
        "otherOptions": {
          "includeLinkToWorkflow": true
        }
      },
      "id": "3caff29a-fa76-47ce-aeed-bc6a55a244f6",
      "name": "Post notes issue",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        2640,
        160
      ]
    }
  ],
  "connections": {
    "Every 15 minutes": {
      "main": [
        [
          {
            "node": "Google Calendar: recent meetings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar: recent meetings": {
      "main": [
        [
          {
            "node": "Pick meeting",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pick meeting": {
      "main": [
        [
          {
            "node": "Zoom: recording files",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Zoom: recording files": {
      "main": [
        [
          {
            "node": "Pick transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pick transcript": {
      "main": [
        [
          {
            "node": "Download transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download transcript": {
      "main": [
        [
          {
            "node": "HubSpot: match attendees",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post notes issue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot: match attendees": {
      "main": [
        [
          {
            "node": "Build notes request",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post notes issue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build notes request": {
      "main": [
        [
          {
            "node": "Claude: draft notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude: draft notes": {
      "main": [
        [
          {
            "node": "Parse notes",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Parse notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse notes": {
      "main": [
        [
          {
            "node": "Ready for review?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ready for review?": {
      "main": [
        [
          {
            "node": "Ask rep to approve",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post notes issue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ask rep to approve": {
      "main": [
        [
          {
            "node": "HubSpot: create note",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot: create note": {
      "main": [
        [],
        [
          {
            "node": "Post notes issue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
