{
  "meta": {
    "description": "Turn a project meeting transcript into a structured execution brief using Claude: transcript quality check, executive summary, action items, risks, and follow-up message draft. This Community Edition returns the brief as plain text from a webhook response.\n\nFor PMs who need more than a generic meeting summary \u2014 this workflow preserves uncertainty, labels unclear ownership, and avoids inventing decisions or deadlines.\n\nWhat this workflow produces:\n- Transcript quality check (reliability rating before analysis begins)\n- Executive brief (5-8 bullets: confirmed decisions, next steps, unresolved issues, risks)\n- Action items table (owner, deadline, evidence, confidence, confirmation needed)\n- Risks table (evidence, severity, confidence, recommended next step)\n- Follow-up message draft (confirmed actions separated from items to confirm)\n\nThe workflow does not invent clarity. Unassigned items are labeled Unassigned. Vague commitments are flagged as Low confidence. If the meeting was messy, the brief reflects that.\n\nRequirements:\n- Anthropic API key (console.anthropic.com)\n- Set as HTTP Header Auth credential: header name x-api-key, value your API key\n- n8n (self-hosted or n8n Cloud)\n\nPrivacy note: Do not submit confidential, regulated, client-sensitive, or proprietary transcripts unless your organization permits it and your API data settings are appropriate. Transcript content is sent to the Anthropic API.\n\nCommunity Edition is provided as-is. For the documented version with email delivery, sample files, and setup guide, see PM Execution Tools at pmexecution.com.",
    "templateCredsSetupCompleted": false
  },
  "name": "Create a Project Execution Brief from a Meeting Transcript with Claude",
  "tags": [
    "project-management",
    "meeting",
    "execution-brief",
    "claude",
    "anthropic"
  ],
  "nodes": [
    {
      "id": "d81c22bb-02c2-4efe-adf1-f01c9c42740b",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -368,
        64
      ],
      "parameters": {
        "width": 480,
        "height": 864,
        "content": "## Create a Project Execution Brief from a Meeting Transcript with Claude\n\n### How it works\n\nThis workflow accepts a meeting transcript through a webhook, validates that the submitted payload contains usable transcript content, and branches based on the result. For valid input, it sends the transcript to Claude via Anthropic's Messages API, extracts the generated project execution brief, and returns it to the webhook caller. If validation fails, it immediately returns an error response.\n\n### Setup steps\n\n- Configure the webhook endpoint and ensure callers send the expected transcript field in the request body.\n- Add Anthropic API credentials or headers for the HTTP Request node, including the API key, Anthropic version, and content type required by the Messages API.\n- Review the validation Code node so its required fields and error messages match the payload format you expect.\n- Confirm the Claude request body, model name, token limits, and prompt instructions are appropriate for producing a project execution brief.\n\n### Customization\n\nAdjust the Claude prompt to change the brief structure, such as adding owners, milestones, risks, dependencies, or executive summary sections. You can also modify the validation logic to require metadata like meeting date, attendees, or project name."
      },
      "typeVersion": 1
    },
    {
      "id": "496a13da-5b98-4350-bef8-5893f804ff4d",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        192,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 320,
        "content": "## Receive and validate transcript\n\nWebhook entry point that receives the transcript submission, checks that the input is valid, and routes the workflow to either the success path or the error response."
      },
      "typeVersion": 1
    },
    {
      "id": "6bd0cf85-2458-4e5b-aa42-dff11b588064",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        880,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 320,
        "content": "## Generate and return brief\n\nSuccess-path cluster that calls Claude to create the project execution brief, extracts the generated text from the API response, and returns the brief to the webhook caller."
      },
      "typeVersion": 1
    },
    {
      "id": "bb56c0f7-3cc4-491a-854e-9301370e8fc1",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        880,
        416
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 336,
        "content": "## Return validation error\n\nLower branch that sends an immediate webhook response when the transcript input fails validation."
      },
      "typeVersion": 1
    },
    {
      "id": "webhook-trigger",
      "name": "When Transcript Submitted",
      "type": "n8n-nodes-base.webhook",
      "notes": "POST webhook. Send your transcript in the request body as field name: transcript. Activate the workflow to get your Production URL. Privacy: do not submit confidential or regulated meeting content.",
      "position": [
        240,
        300
      ],
      "parameters": {
        "path": "execution-brief",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "validate-input",
      "name": "Check Transcript Validity",
      "type": "n8n-nodes-base.code",
      "notes": "Checks that the transcript field is present and has enough content to analyze. Returns valid: true/false.",
      "position": [
        460,
        300
      ],
      "parameters": {
        "jsCode": "const body = $input.first().json.body;\nconst transcript = body.transcript || body.data || '';\n\nif (!transcript || transcript.trim().length < 50) {\n  return [{ json: { valid: false, error: 'Transcript is too short or empty. Please provide a meeting transcript of at least a few exchanges.' } }];\n}\n\nreturn [{ json: { valid: true, transcript: transcript.trim() } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "if-valid",
      "name": "If Transcript is Valid",
      "type": "n8n-nodes-base.if",
      "position": [
        680,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "valid-check",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.valid }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "claude-api",
      "name": "Post to Claude API",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Calls the Anthropic API. Requires an HTTP Header Auth credential \u2014 header name: x-api-key, value: your Anthropic API key from console.anthropic.com. API cost depends on transcript length and your Anthropic pricing tier. Update the model field if Anthropic releases a newer model version.",
      "position": [
        928,
        224
      ],
      "parameters": {
        "url": "https://api.anthropic.com/v1/messages",
        "body": "={\n  \"model\": \"claude-sonnet-4-5-20251022\",\n  \"max_tokens\": 4000,\n  \"system\": \"You are analyzing a project meeting transcript.\\n\\nYour role is to convert the transcript into a practical project execution brief for a project manager.\\n\\nYour priority is accuracy, not completeness.\\n\\nDo not make the meeting look cleaner, more organized, or more decisive than it actually was.\\n\\nDo not invent owners, deadlines, decisions, agreement, risks, dependencies, or commitments.\\n\\nIf information is missing, unclear, vague, or only implied, say so directly.\\n\\nNever present an assumption as fact.\\n\\nDo not create action items, risks, or dependencies simply to fill a section.\\n\\nEvidence labels: Explicit / Implied / Unclear\\nConfidence levels: High (directly stated) / Medium (strongly implied) / Low (vague or requires confirmation)\\n\\nDo not treat the following as confirmed decisions: hedge language like 'sounds like we are going with', 'approved in principle', 'I was told', 'apparently', 'we will figure it out', 'same as last time'.\\n\\nOutput the following sections only:\\n\\n## Transcript Quality Check\\nRate: Good / Usable with gaps / Poor. One short paragraph.\\n\\n## Executive Brief\\n5-8 bullets: confirmed decisions, confirmed next steps, major unresolved issues, meaningful risks.\\n\\n## Action Items\\nTable: Task | Owner | Deadline | Evidence | Confidence | Confirmation Needed\\nIf no owner stated, write Unassigned. If no deadline stated, write Not stated.\\n\\n## Risks\\nTable: Risk | Evidence | Severity (High/Medium/Low) | Confidence | Recommended Next Step\\nOnly include risks with transcript evidence.\\n\\n## Follow-Up Message Draft\\nCalm, direct, professional. Confirmed actions in the body. Uncertain items under Items to Confirm. Include a suggested subject line.\\n\\nEnd with: Analysis is based only on the provided transcript. Confirm with meeting participants where ownership, deadlines, decisions, or commitments are unclear or unverified.\",\n  \"messages\": [\n    {\n      \"role\": \"user\",\n      \"content\": {{ JSON.stringify('Here is the meeting transcript:\\n\\n' + $json.transcript) }}\n    }\n  ]\n}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "extract-brief",
      "name": "Parse Brief from Response",
      "type": "n8n-nodes-base.code",
      "notes": "Extracts the text content from the Claude API response. Handles the content array format returned by the Anthropic API.",
      "position": [
        1152,
        224
      ],
      "parameters": {
        "jsCode": "const response = $input.first().json;\n\n// Extract the text content from the Claude API response\nlet briefText = '';\n\nif (response.content && Array.isArray(response.content)) {\n  for (const block of response.content) {\n    if (block.type === 'text') {\n      briefText += block.text;\n    }\n  }\n}\n\nif (!briefText) {\n  return [{ json: { success: false, error: 'No content returned from Claude API. Check your API key and credits.' } }];\n}\n\nreturn [{ json: { success: true, brief: briefText } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "respond-with-brief",
      "name": "Return Brief to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "notes": "Returns the execution brief as plain text in the HTTP response. The caller receives the formatted brief directly. To email the brief instead, replace this node with an Email Send node.",
      "position": [
        1376,
        224
      ],
      "parameters": {
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/plain; charset=utf-8"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "={{ $json.brief }}"
      },
      "typeVersion": 1
    },
    {
      "id": "respond-with-error",
      "name": "Return Error to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "notes": "Returns an error message if the transcript is too short or empty.",
      "position": [
        928,
        592
      ],
      "parameters": {
        "options": {
          "responseCode": 400,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/plain; charset=utf-8"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "={{ $json.error }}"
      },
      "typeVersion": 1
    }
  ],
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true
  },
  "versionId": "community-1.0",
  "staticData": null,
  "connections": {
    "Post to Claude API": {
      "main": [
        [
          {
            "node": "Parse Brief from Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Transcript is Valid": {
      "main": [
        [
          {
            "node": "Post to Claude API",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Return Error to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Transcript Validity": {
      "main": [
        [
          {
            "node": "If Transcript is Valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Brief from Response": {
      "main": [
        [
          {
            "node": "Return Brief to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Transcript Submitted": {
      "main": [
        [
          {
            "node": "Check Transcript Validity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}