How to Build a Claude-Powered Meeting Notes Summarizer
Building a Claude-powered meeting notes summarizer means feeding a raw transcript (or live captions) to Claude with a prompt that extracts a summary, decisions, and action items, then returning that as structured text or JSON your app can render. The core pipeline is: capture or upload the transcript, chunk it if it's long, send it to Claude with a well-designed system prompt, and parse the response into a usable format.
This guide walks through each step, including how to handle transcripts that exceed context limits, how to get consistent structured output instead of free-form prose, and how to wire it into a working API endpoint without managing your own Anthropic account or billing.
What the summarizer actually needs to produce
Before writing any code, define the output shape. A generic "summarize this meeting" prompt gives you inconsistent paragraphs that are hard to render in a UI. Most teams building this feature want four consistent blocks:
- Summary — 2-4 sentences on what the meeting was about
- Decisions — concrete things that were agreed, not discussed
- Action items — who owns what, with a deadline if mentioned
- Open questions — things explicitly left unresolved
Locking this structure in up front makes everything downstream (storage, rendering, Slack notifications) much simpler.
Step 1: Get the transcript into text
Your source is usually one of:
- A transcript file from Zoom, Google Meet, or Otter.ai
- Raw audio you've already run through a speech-to-text service
- Live captions streamed in chunks during the call
For this guide, assume you already have a plain-text transcript, since speech-to-text is a separate concern. If speaker labels are present ("Alex: ...", "Priya: ..."), keep them — Claude uses them to attribute action items correctly.
Step 2: Chunk long transcripts
A one-hour meeting can easily run 8,000-15,000 words. If your transcript fits comfortably in context (most Claude models handle well over 100K tokens), you can send it in one call. For very long calls, multi-hour workshops, or when you're batching several meetings together, split the transcript into chunks of roughly 3,000-4,000 words with some overlap, summarize each chunk, then run a final "merge summaries" pass over the chunk summaries.
function chunkTranscript(text, maxWords = 3500, overlap = 200) {
const words = text.split(/\s+/);
const chunks = [];
for (let i = 0; i < words.length; i += maxWords - overlap) {
chunks.push(words.slice(i, i + maxWords).join(" "));
}
return chunks;
}
For most team meetings and standups, one chunk is enough — only reach for this pattern on long-form calls.
Step 3: Design the prompt for structured output
Ask for JSON directly rather than parsing prose with regex. This is the single biggest reliability improvement you can make.
You are a meeting notes assistant. Read the transcript below and return
ONLY valid JSON with this exact shape:
{
"summary": "string, 2-4 sentences",
"decisions": ["string", ...],
"action_items": [
{ "owner": "string", "task": "string", "due": "string or null" }
],
"open_questions": ["string", ...]
}
Rules:
- If no speaker is mentioned for an action item, use "unassigned".
- If no deadline is mentioned, use null for "due".
- Do not include commentary outside the JSON object.
Transcript:
<<<TRANSCRIPT>>>
This gives you a predictable schema you can validate and store directly in a database row.
Step 4: Call the API
Once you have an API key, the request is a single HTTP call. Here's an example using SubToAPI, which exposes Claude through a standard REST API with an sub_live_... key instead of requiring you to set up billing and provisioning directly with Anthropic:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "You are a meeting notes assistant... Transcript: ..."
}
]
}'
The response body contains the model's reply in content. Because you asked for strict JSON in the prompt, parse it with JSON.parse() and validate the shape before saving it. If parsing fails occasionally (it will, rarely), retry once with a reminder: "Return ONLY valid JSON, no markdown fences."
async function summarizeMeeting(transcript) {
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 1024,
messages: [{ role: "user", content: buildPrompt(transcript) }],
}),
});
const data = await res.json();
return JSON.parse(data.content[0].text);
}
See /docs/quickstart for the full request/response reference and /docs/messages for message formatting details.
Step 5: Stream for live summaries
If you want to show a running summary during the call rather than waiting until it ends, stream the response token-by-token instead of waiting for the full completion. This matters most for "live notes" features where users see decisions appear as they're made. SubToAPI supports server-sent event streaming on the same endpoint — details are in /docs/streaming.
Step 6: Handle multi-speaker attribution carefully
A common failure mode is Claude attributing an action item to the wrong person when speaker labels are inconsistent (e.g., "John" vs "J." vs no label at all). Two fixes help:
- Normalize speaker names before sending the transcript (map aliases to one canonical name)
- Add an explicit rule in the prompt: "Only assign an action item to a named speaker if they are the one proposing or accepting the task, not just mentioned."
Step 7: Post-process and deliver
Once you have validated JSON, the typical delivery paths are:
- Store it alongside the meeting record in your database
- Post decisions and action items to Slack via a formatted message
- Create tickets automatically from
action_itemsusing a tool integration (see /docs/tools if you want Claude itself to call a ticket-creation function) - Email a daily digest of all meetings summarized that day
Why not call Anthropic's API directly?
You can build all of this directly against Anthropic's API too. Teams reach for SubToAPI when they want a single dashboard across multiple seats, per-key usage metadata to see which feature or team is consuming tokens, and a simple sub_live_... key instead of managing organization-level credentials. Plans start at Solo €9/month, with Team (€19/seat) and Scale (€49/seat) tiers for larger teams — see /pricing. There's a free trial at /signup if you want to test the full pipeline above against a live endpoint.
questions
Do I need a large context window for long meetings? Most single meetings fit in context without chunking. Reserve chunking for multi-hour workshops or when batching several transcripts into one summarization run.
How do I stop Claude from inventing action items that weren't said? Add an explicit instruction like "Only include action items explicitly stated or agreed to; do not infer tasks that weren't discussed," and keep the output schema strict so there's no room for narrative embellishment.
Can I get structured JSON reliably instead of parsing prose? Yes — specify the exact JSON shape in the prompt, instruct the model to return only that object with no surrounding text, and validate/retry on parse failure. This is more reliable than regex-parsing free-form summaries.