{
  "name": "Lumeor | Law firm client intake with conflict check",
  "nodes": [
    {
      "parameters": {
        "content": "## Law firm client intake starter\n\nA website or Jotform intake posts to the webhook. The workflow checks that the non-engagement notice was acknowledged, has Claude write a neutral two-sentence summary and pull out every named person and organization with their role, then runs a **conflict check (placeholder)** against your practice management system for each name and its variants. The intake coordinator gets one Microsoft Teams message with the summary, deadline flag and hit list, plus **Conflicts cleared** and **Decline** buttons. Only a cleared intake gets the consultation scheduling link.\n\n### Setup\n1. Point your intake form at the **Intake form** webhook. Fields: `name`, `email`, `phone`, `other_parties` (array or comma-separated), `matter_type`, `key_dates`, `description`, `referral_source`, `notice_acknowledged` (true/false).\n2. **Conflict check (placeholder)**: the URL is set up for Clio's contacts search (`https://app.clio.com/api/v4/contacts.json?query=`). Create a generic OAuth2 credential for your Clio API app, or change the URL and auth for MyCase or your system. Extend it to search matters and related parties before relying on it. This step does not replace your firm's conflict procedure.\n3. Replace `REPLACE_WITH_TEAMS_CHAT_ID`, `REPLACE_WITH_TEAMS_TEAM_ID`, `REPLACE_WITH_TEAMS_CHANNEL_ID`, `REPLACE_WITH_SCHEDULING_LINK` and `REPLACE_WITH_FIRM_NAME`.\n4. Credentials: Anthropic API, OAuth2 for your practice management API, Microsoft Teams OAuth2, Gmail OAuth2.\n\nModel: `claude-opus-5-5`. `claude-haiku-5-5` is a cheaper option for high-volume, simple classification, but party extraction from long stories benefits from the stronger model.\n\nProspect data goes to the Anthropic API, which does not train on API data by default. Confirm this fits your firm's confidentiality policy, and consider self-hosting n8n.\n\nTemplate by Lumeor Studio, https://www.lumeor.studio/guides/automate-client-intake-for-law-firms/",
        "height": 900,
        "width": 540,
        "color": 7
      },
      "id": "d22fe001-c375-4ad7-99db-8bf0882824cf",
      "name": "About this template",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        -240
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "law-intake",
        "options": {}
      },
      "id": "a9ece9ee-70c1-4b9f-b5e1-7e89acb31899",
      "name": "Intake form",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        300
      ],
      "webhookId": "652286e4-0663-4ac0-9bb6-e4e667267942"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const clean = (s) => (typeof s === 'string' ? s.trim() : '');\nconst toList = (v) => (Array.isArray(v) ? v : clean(v).split(/[,;\\n]/)).map(clean).filter(Boolean);\nconst nul = (type) => ({ anyOf: [{ type }, { type: 'null' }] });\n\nconst schema = {\n  type: 'object',\n  additionalProperties: false,\n  required: ['summary', 'practice_area', 'deadline_mentioned', 'deadline_date', 'deadline_note', 'parties'],\n  properties: {\n    summary: { type: 'string', description: 'Two neutral sentences. No assessment of the merits.' },\n    practice_area: { type: 'string', enum: ['family', 'personal_injury', 'employment', 'business', 'real_estate', 'estate_planning', 'criminal', 'immigration', 'intellectual_property', 'other'] },\n    deadline_mentioned: { type: 'boolean', description: 'True if any hearing, filing, court date or limitations concern is mentioned.' },\n    deadline_date: { ...nul('string'), description: 'ISO date (YYYY-MM-DD) if a specific date is stated, else null.' },\n    deadline_note: nul('string'),\n    parties: {\n      type: 'array',\n      items: {\n        type: 'object',\n        additionalProperties: false,\n        required: ['name', 'kind', 'role'],\n        properties: {\n          name: { type: 'string' },\n          kind: { type: 'string', enum: ['person', 'organization'] },\n          role: { type: 'string', enum: ['prospective_client', 'opposing_party', 'related_party', 'witness', 'other'] },\n        },\n      },\n    },\n  },\n};\n\nreturn $input.all().map((item, i) => {\n  const b = item.json.body || {};\n  const issues = [];\n  const ack = b.notice_acknowledged === true || /^(true|yes|on|1)$/i.test(String(b.notice_acknowledged));\n  if (!ack) issues.push('Non-engagement notice was not acknowledged');\n  if (!clean(b.name)) issues.push('Missing name');\n  if (!clean(b.email) && !clean(b.phone)) issues.push('No email or phone');\n  if (clean(b.description).length < 20) issues.push('Description too short to screen');\n\n  const intake = {\n    name: clean(b.name),\n    email: clean(b.email),\n    phone: clean(b.phone),\n    otherParties: toList(b.other_parties),\n    matterType: clean(b.matter_type),\n    keyDates: clean(b.key_dates),\n    referralSource: clean(b.referral_source),\n    description: clean(b.description),\n    noticeAcknowledged: ack,\n    receivedAt: $now.toISO(),\n  };\n\n  const text = [\n    'Prospective client: ' + intake.name,\n    'Other parties listed on the form: ' + (intake.otherParties.join('; ') || 'none'),\n    'Matter type selected: ' + intake.matterType,\n    'Key dates: ' + intake.keyDates,\n    'Description:',\n    intake.description,\n  ].join('\\n');\n\n  return {\n    json: {\n      valid: issues.length === 0,\n      issues,\n      intake,\n      claudeRequest: {\n        model: 'claude-opus-5-5',\n        max_tokens: 3000,\n        system: 'You prepare law firm intake records for a conflict check. List every person and organization named anywhere in the intake, including the prospective client, with their role. Write a neutral summary. Do not assess the merits or give legal advice. If a date is ambiguous, set deadline_date to null and explain in deadline_note.',\n        messages: [{ role: 'user', content: '<intake>\\n' + text + '\\n</intake>' }],\n        output_config: { format: { type: 'json_schema', schema } },\n      },\n    },\n    pairedItem: { item: i },\n  };\n});\n"
      },
      "id": "7e32db23-888f-4ec4-8889-a54863d00d5a",
      "name": "Validate intake and build prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "75c0bd67-33cf-4fb3-8ccb-6a2b5db15575",
              "leftValue": "={{ $json.valid }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f4616432-48dd-4652-a11c-1fda3e192d59",
      "name": "Intake complete?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "anthropicApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.claudeRequest) }}",
        "options": {
          "timeout": 120000
        }
      },
      "id": "4db0c9dd-e1fa-4628-a326-d6315b5209f1",
      "name": "Claude: summarize and extract parties",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        200
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Pull the structured JSON out of a Messages API response.\n// Opus 5.5 always thinks, so a (possibly empty) thinking block can come before the text block.\nfunction parseClaude(res) {\n  if (!res || typeof res !== 'object') return { error: 'Empty response from the Messages API' };\n  if (res.stop_reason === 'refusal') {\n    return { error: 'Model declined the request (refusal' + (res.stop_details?.category ? ': ' + res.stop_details.category : '') + ')' };\n  }\n  if (res.stop_reason === 'max_tokens') return { error: 'Response hit max_tokens before the JSON was complete' };\n  const block = (res.content || []).find((b) => b.type === 'text');\n  if (!block) return { error: 'No text block in the response' };\n  try {\n    return { data: JSON.parse(block.text) };\n  } catch (e) {\n    return { error: 'Could not parse model JSON: ' + e.message };\n  }\n}\n\nconst SUFFIXES = /\\b(l\\.?l\\.?c|inc|incorporated|corp|corporation|co|company|ltd|limited|llp|lp|pc|p\\.c|pllc|plc|the)\\b\\.?/gi;\nconst NICKNAMES = { william: ['bill', 'will', 'billy'], robert: ['bob', 'rob', 'bobby'], richard: ['rick', 'dick', 'rich'], michael: ['mike'], james: ['jim', 'jimmy'], elizabeth: ['liz', 'beth', 'betsy'], katherine: ['kate', 'katie', 'kathy'], catherine: ['cathy', 'kate'], jennifer: ['jen', 'jenny'], joseph: ['joe'], thomas: ['tom'], margaret: ['maggie', 'peggy'], christopher: ['chris'], anthony: ['tony'], daniel: ['dan', 'danny'], patricia: ['pat', 'trish'] };\nconst reverseNick = {};\nfor (const [full, nicks] of Object.entries(NICKNAMES)) for (const n of nicks) (reverseNick[n] ??= []).push(full);\n\nconst normalizeOrg = (s) => s.replace(/[.,]/g, ' ').replace(SUFFIXES, ' ').replace(/\\s+/g, ' ').trim();\nconst normalizePerson = (s) => s.replace(/\\b(jr|sr|ii|iii|iv|esq|dr|mr|mrs|ms)\\b\\.?/gi, ' ').replace(/[.,]/g, ' ').replace(/\\s+/g, ' ').trim();\n\nconst out = [];\nconst items = $input.all();\nfor (let i = 0; i < items.length; i++) {\n  const src = $('Validate intake and build prompt').itemMatching(i).json;\n  const { data, error } = parseClaude(items[i].json);\n  // If extraction failed, still search every name typed into the form. The card will say so.\n  const parties = error ? [] : [...data.parties];\n  for (const n of src.intake.otherParties) if (!parties.some((p) => p.name.toLowerCase() === n.toLowerCase())) parties.push({ name: n, kind: /\\b(llc|inc|corp|ltd|llp|company|co)\\b/i.test(n) ? 'organization' : 'person', role: 'other' });\n  if (!parties.some((p) => p.role === 'prospective_client')) parties.unshift({ name: src.intake.name, kind: 'person', role: 'prospective_client' });\n\n  const seen = new Set();\n  for (const p of parties) {\n    const variants = new Set();\n    if (p.kind === 'organization') {\n      variants.add(normalizeOrg(p.name));\n    } else {\n      const clean = normalizePerson(p.name);\n      variants.add(clean);\n      const parts = clean.split(' ');\n      if (parts.length >= 2) {\n        const first = parts[0].toLowerCase();\n        const last = parts[parts.length - 1];\n        for (const alt of [...(NICKNAMES[first] || []), ...(reverseNick[first] || [])]) variants.add(alt[0].toUpperCase() + alt.slice(1) + ' ' + last);\n        variants.add(last); // weaker match, flagged in the hit list\n      }\n    }\n    for (const v of variants) {\n      const key = v.toLowerCase();\n      if (!v || seen.has(key)) continue;\n      seen.add(key);\n      out.push({ json: { searchTerm: v, party: p, lastNameOnly: p.kind === 'person' && !v.includes(' '), extraction: data || null, extractionError: error || null }, pairedItem: { item: i } });\n    }\n  }\n}\nreturn out;\n"
      },
      "id": "ad89f2ec-4dfe-4dc7-a609-1563ee2bbba9",
      "name": "Build conflict search terms",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        200
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://app.clio.com/api/v4/contacts.json",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "query",
              "value": "={{ $json.searchTerm }}"
            },
            {
              "name": "fields",
              "value": "id,name,type"
            },
            {
              "name": "limit",
              "value": "25"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "id": "2602d1b1-22cc-4cf2-87b6-9a0ad5dd80f9",
      "name": "Conflict check (placeholder): search PMS",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        200
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// One item per search term comes in; one summary item goes out.\n// Failed searches arrive here too (the HTTP node continues on error) so they can be flagged, never silently dropped.\nconst intakeSrc = $('Validate intake and build prompt').first().json.intake;\nconst first = $('Build conflict search terms').first().json;\nconst extraction = first.extraction || { summary: '(AI extraction failed: ' + first.extractionError + ')', practice_area: 'unknown', parties: [] };\n\nconst hits = [];\nconst failedTerms = [];\n$input.all().forEach((item, i) => {\n  const term = $('Build conflict search terms').itemMatching(i).json;\n  if (item.json.error) { failedTerms.push(term.searchTerm); return; }\n  // Clio returns { data: [...] }. Adjust here for other systems.\n  const records = Array.isArray(item.json.data) ? item.json.data : [];\n  for (const r of records) {\n    hits.push({\n      searchedFor: term.searchTerm,\n      party: term.party?.name,\n      partyRole: term.party?.role,\n      weakMatch: Boolean(term.lastNameOnly),\n      recordId: r.id,\n      recordName: r.name,\n      recordType: r.type,\n    });\n  }\n});\n\nconst strong = hits.filter((h) => !h.weakMatch);\nconst daysOut = extraction.deadline_date ? (new Date(extraction.deadline_date) - new Date()) / 86400000 : null;\nconst deadlineSoon = daysOut !== null && daysOut <= 14;\nconst esc = (s) => String(s ?? '').replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' })[c]);\nconst rows = hits.length\n  ? hits.map((h) => '<li>' + esc(h.party) + ' (' + esc(h.partyRole) + ') matched <b>' + esc(h.recordName) + '</b> [' + esc(h.recordType) + ' ' + esc(h.recordId) + ']' + (h.weakMatch ? ' <i>last name only</i>' : '') + '</li>').join('')\n  : '<li>No matches found in the contact search.</li>';\n\nconst card = [\n  failedTerms.length ? '<b>SEARCH FAILED for: ' + esc(failedTerms.join(', ')) + '. Do not clear until these are searched by hand.</b><br>' : '',\n  deadlineSoon ? '<b>DEADLINE WITHIN 14 DAYS: ' + esc(extraction.deadline_date) + '</b><br>' : '',\n  '<b>New intake:</b> ' + esc(intakeSrc.name) + ' | ' + esc(extraction.practice_area),\n  '<br><b>Summary:</b> ' + esc(extraction.summary),\n  extraction.deadline_mentioned ? '<br><b>Deadline note:</b> ' + esc(extraction.deadline_note || extraction.deadline_date) : '',\n  '<br><b>Parties:</b> ' + esc((extraction.parties || []).map((p) => p.name + ' (' + p.role + ')').join(', ')),\n  '<br><b>Conflict search hits (' + strong.length + ' strong, ' + (hits.length - strong.length) + ' weak):</b><ul>' + rows + '</ul>',\n  'Clear only after your own conflict review.',\n].join('');\n\nreturn [{ json: { intake: intakeSrc, extraction, hits, failedTerms, strongHitCount: strong.length, deadlineSoon, card } }];\n"
      },
      "id": "f8c795b7-3b5c-40a4-bb44-6964eca6ef8a",
      "name": "Build conflict hit list",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        200
      ]
    },
    {
      "parameters": {
        "resource": "chatMessage",
        "operation": "sendAndWait",
        "chatId": {
          "__rl": true,
          "value": "REPLACE_WITH_TEAMS_CHAT_ID",
          "mode": "id"
        },
        "message": "={{ $json.card }}",
        "approvalOptions": {
          "values": {
            "approvalType": "double",
            "approveLabel": "Conflicts cleared",
            "disapproveLabel": "Decline"
          }
        },
        "options": {
          "limitWaitTime": {
            "values": {
              "limitType": "afterTimeInterval",
              "resumeAmount": 72,
              "resumeUnit": "hours"
            }
          }
        }
      },
      "id": "4de27873-7a01-4c7c-9144-dd4181d46337",
      "name": "Teams: attorney clears conflicts",
      "type": "n8n-nodes-base.microsoftTeams",
      "typeVersion": 2,
      "position": [
        1540,
        200
      ],
      "webhookId": "fd2e41d6-3734-4054-8fe4-71b5ab880a15"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "a9b56613-be42-4d46-af80-4a9f68cb857d",
                    "leftValue": "={{ String($json.data?.approved) }}",
                    "rightValue": "true",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "cleared"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "b9926de3-0113-41b0-b79a-b17b10a4e400",
                    "leftValue": "={{ String($json.data?.approved) }}",
                    "rightValue": "false",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "declined"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "Fallback"
        }
      },
      "id": "a9541e15-6ba5-44c3-9131-6e3753831f83",
      "name": "Decision",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1760,
        200
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $('Build conflict hit list').item.json.intake.email }}",
        "subject": "=Next step with REPLACE_WITH_FIRM_NAME",
        "emailType": "text",
        "message": "=Hello {{ $('Build conflict hit list').item.json.intake.name }},\n\nThank you for contacting REPLACE_WITH_FIRM_NAME. We would like to schedule a consultation. Please choose a time here: REPLACE_WITH_SCHEDULING_LINK\n\nUntil a consultation takes place and an engagement letter is signed, we do not represent you. Please do not send us confidential documents before then.\n\nREPLACE_WITH_FIRM_NAME",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "f8c4e23c-79ed-4332-9db0-0656860c59d5",
      "name": "Gmail: send scheduling link",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1980,
        60
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $('Build conflict hit list').item.json.intake.email }}",
        "subject": "=Your inquiry to REPLACE_WITH_FIRM_NAME",
        "emailType": "text",
        "message": "=Hello {{ $('Build conflict hit list').item.json.intake.name }},\n\nThank you for contacting REPLACE_WITH_FIRM_NAME. We are not able to assist with this matter. This is not a judgment about your situation, and deadlines may apply to your matter, so we encourage you to contact another attorney promptly. Your state bar association runs a lawyer referral service.\n\nREPLACE_WITH_FIRM_NAME",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "feaefafc-29b1-4393-ad33-33fc9dd0f7d8",
      "name": "Gmail: send polite decline",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1980,
        220
      ]
    },
    {
      "parameters": {
        "resource": "channelMessage",
        "operation": "create",
        "teamId": {
          "__rl": true,
          "value": "REPLACE_WITH_TEAMS_TEAM_ID",
          "mode": "id"
        },
        "channelId": {
          "__rl": true,
          "value": "REPLACE_WITH_TEAMS_CHANNEL_ID",
          "mode": "id"
        },
        "contentType": "html",
        "message": "=<b>Intake needs manual handling</b><br>\nName: {{ $('Validate intake and build prompt').first().json.intake.name }}<br>\nEmail: {{ $('Validate intake and build prompt').first().json.intake.email }} | Phone: {{ $('Validate intake and build prompt').first().json.intake.phone }}<br>\nReason: {{ $json.issues ? $json.issues.join('; ') : ($json.error ? 'Claude API error: ' + ($json.error.message ?? JSON.stringify($json.error)) : 'No attorney decision within 72 hours') }}",
        "options": {}
      },
      "id": "c537e671-3756-44cf-9723-3a2d92c92513",
      "name": "Teams: intake exceptions",
      "type": "n8n-nodes-base.microsoftTeams",
      "typeVersion": 2,
      "position": [
        1100,
        520
      ]
    }
  ],
  "connections": {
    "Intake form": {
      "main": [
        [
          {
            "node": "Validate intake and build prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate intake and build prompt": {
      "main": [
        [
          {
            "node": "Intake complete?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Intake complete?": {
      "main": [
        [
          {
            "node": "Claude: summarize and extract parties",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Teams: intake exceptions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude: summarize and extract parties": {
      "main": [
        [
          {
            "node": "Build conflict search terms",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Teams: intake exceptions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build conflict search terms": {
      "main": [
        [
          {
            "node": "Conflict check (placeholder): search PMS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Conflict check (placeholder): search PMS": {
      "main": [
        [
          {
            "node": "Build conflict hit list",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build conflict hit list": {
      "main": [
        [
          {
            "node": "Teams: attorney clears conflicts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Teams: attorney clears conflicts": {
      "main": [
        [
          {
            "node": "Decision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decision": {
      "main": [
        [
          {
            "node": "Gmail: send scheduling link",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Gmail: send polite decline",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Teams: intake exceptions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
