How to Build a Meeting Notes Summarizer
A meeting notes summarizer takes a raw transcript or recording-derived text and turns it into a short, structured summary: key decisions, action items, and owners. Building one is mostly a prompting and data-pipeline problem, not a model-training problem — you don't need to fine-tune anything. You feed a transcript into an LLM with a well-structured prompt, parse the structured output, and store or send it wherever your team already works (Slack, Notion, email).
This guide walks through the actual pieces: getting the transcript text, designing a prompt that produces consistent structured output, calling the API, and wiring it into a simple pipeline you can run after every meeting.
What you actually need to build
A meeting notes summarizer has four components:
- Transcript input — from a transcription service (Whisper, Otter, Zoom's built-in transcript, Fireflies, etc.) or a manually pasted text file.
- A summarization prompt — instructions that tell the model exactly what structure to return.
- An LLM call — the actual API request that generates the summary.
- Output handling — parsing the response and pushing it somewhere useful.
Most of the "hard" work is in step 2. Getting consistent, parseable output from a model requires more care than people expect on the first try.
Step 1: Get clean transcript text
If you're using a transcription tool, export the transcript as plain text with speaker labels if available. Speaker labels matter — they let the model attribute action items to the right person ("Sarah will send the proposal by Friday" is much more useful than an unattributed sentence).
If your transcript is long (over an hour of meeting), consider chunking it and summarizing in two passes: first summarize each chunk, then summarize the summaries. Most current Claude models handle long context well, so for meetings under roughly two hours you can usually send the whole transcript in one call.
Step 2: Design the prompt
The biggest mistake in building a summarizer is asking for a generic summary. You want structured output you can parse programmatically. Ask for JSON explicitly, and give the model a schema:
You are a meeting notes assistant. Given a meeting transcript, extract:
1. A 2-3 sentence overview of what the meeting was about.
2. Key decisions made (as a list).
3. Action items, each with: task, owner (if mentioned), and due date (if mentioned).
4. Open questions that were raised but not resolved.
Return ONLY valid JSON in this exact shape:
{
"overview": string,
"decisions": string[],
"action_items": [{ "task": string, "owner": string | null, "due_date": string | null }],
"open_questions": string[]
}
Transcript:
<transcript text here>
Being explicit about "owner: null if not mentioned" prevents the model from inventing names, which is a common failure mode when summarizing vague or cross-talking transcripts.
Step 3: Call the API
Here's a working example using SubToAPI, which lets you call Claude models through a standard HTTPS API with an application key (sub_live_...) instead of managing provider-specific auth:
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: `You are a meeting notes assistant... [prompt from Step 2]\n\nTranscript:\n${transcriptText}`
}
]
})
});
const data = await res.json();
const summary = JSON.parse(data.content[0].text);
If you want to guarantee valid JSON structure every time instead of relying on prompt instructions alone, use tool calling — define a tool schema for the summary shape and force the model to call it. This is more robust for production pipelines where a malformed JSON string would break downstream parsing. SubToAPI supports tool use the same way the underlying Claude API does — see /docs/tools for the request format.
Step 4: Handle the output
Once you have structured JSON, the rest is plumbing:
- Slack: post the overview and action items to a channel using a simple formatted message.
- Notion/Confluence: write each field into a page template via their API.
- Email: render the JSON into HTML and send via your mail provider.
- Database: store the raw transcript, the structured summary, and metadata (meeting date, attendees) for later search.
A minimal action-item formatter for Slack:
function formatActionItems(items) {
return items
.map(i => `• *${i.task}* — ${i.owner ?? "Unassigned"}${i.due_date ? ` (due ${i.due_date})` : ""}`)
.join("\n");
}
Making it reliable in production
A few things matter more than people expect once this moves from a demo to something your team depends on:
- Idempotency: if a meeting gets re-processed (retry after a timeout), don't post duplicate summaries. Hash the transcript or use a meeting ID as a dedup key.
- Streaming for long transcripts: if you're summarizing live or want faster perceived response, stream the output rather than waiting for the full generation — see /docs/streaming.
- Fallback for empty transcripts: short or garbled transcripts (bad audio, no one talked) will produce hallucinated content if you don't add a guard: ask the model to return
"insufficient_content": truewhen the transcript is too short or unclear. - Cost tracking: transcript-heavy calls can use a lot of input tokens. If you're running this across many teams or meetings, track usage per key so you know which workflows are expensive — SubToAPI includes usage metadata per request, visible in the dashboard, so you can see token counts without instrumenting your own logging.
Choosing where to run this
You can call Claude directly through Anthropic's API, or through a layer like SubToAPI that gives you an HTTPS endpoint, API keys scoped per application, and team seats if multiple people on your team are building or maintaining summarization workflows. For a single internal script, direct access is fine. For something more teams depend on — shared keys, usage visibility, multiple environments — a managed layer saves you from building that tooling yourself. Plans start at €9/month for solo use, with team and scale tiers for shared access; see /pricing. You can try it with a free trial at /signup, and the full request format is in /docs/quickstart and /docs/messages.
Questions
Do I need to fine-tune a model to build a meeting notes summarizer? No. Prompt-based summarization with a clear output schema works well for this use case. Fine-tuning adds cost and maintenance without meaningfully improving structured extraction tasks like this.
How do I handle very long meetings (2+ hours)? Chunk the transcript into segments, summarize each chunk separately, then run a second summarization pass over the combined chunk summaries to produce the final structured output.
How do I stop the model from inventing action item owners? Explicitly instruct the model to return null when an owner isn't stated in the transcript, and include that constraint in your JSON schema description — vague prompts are the main cause of invented names and dates.