> ## Documentation Index
> Fetch the complete documentation index at: https://assemblyai.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Meeting notetaker

Transcribe a meeting recording with speaker labels and automatic language detection, identify speakers by name, then send the transcript to LLM Gateway for a formatted summary with action items.

**Products used:** [Pre-recorded STT](/pre-recorded-audio/getting-started/transcribe-an-audio-file) + [speaker diarization](/pre-recorded-audio/label-speakers) + [Speaker Identification](/speech-understanding/speaker-identification) + [language detection](/pre-recorded-audio/language-detection) + [LLM Gateway](/llm-gateway/quickstart)

**Model selection:** This example uses both `universal-3-pro` and `universal-2` for broad language coverage across 99 languages. If your meetings are English-only, you can use `universal-3-pro` alone for the highest accuracy.

<Tabs groupId="language">
  <Tab language="python" title="Python" default>
    ```python expandable theme={null}
    import requests
    import time

    # ── Config ────────────────────────────────────────────────────
    base_url = "https://api.assemblyai.com"
    headers = {"authorization": "YOUR_API_KEY"}

    audio_url = "https://assembly.ai/wildfires.mp3"

    # ── Step 1: Transcribe with speaker labels + language detection ──
    data = {
        "audio_url": audio_url,
        "speech_models": ["universal-3-pro", "universal-2"],
        "language_detection": True,
        "speaker_labels": True,
    }

    response = requests.post(base_url + "/v2/transcript", headers=headers, json=data)
    response.raise_for_status()
    transcript_id = response.json()["id"]

    while True:
        result = requests.get(f"{base_url}/v2/transcript/{transcript_id}", headers=headers).json()
        if result["status"] == "completed":
            break
        elif result["status"] == "error":
            raise RuntimeError(f"Transcription failed: {result['error']}")
        time.sleep(3)

    # ── Step 2: Identify speakers by name ──
    understanding_response = requests.post(
        "https://llm-gateway.assemblyai.com/v1/understanding",
        headers=headers,
        json={
            "transcript_id": transcript_id,
            "speech_understanding": {
                "request": {
                    "speaker_identification": {
                        "speaker_type": "name",
                        "known_values": ["Alice", "Bob"],  # Replace with actual participant names
                    }
                }
            },
        },
    )
    understanding_response.raise_for_status()
    identified = understanding_response.json()

    # ── Step 3: Format identified transcript for the LLM ──
    speaker_transcript = "\n".join(
        f"{u['speaker']}: {u['text']}" for u in identified["utterances"]
    )

    # ── Step 4: Generate meeting notes via LLM Gateway ──
    llm_response = requests.post(
        "https://llm-gateway.assemblyai.com/v1/chat/completions",
        headers=headers,
        json={
            "model": "claude-sonnet-4-5-20250929",
            "messages": [
                {
                    "role": "user",
                    "content": (
                        "You are a meeting notes assistant. Given the transcript below, produce:\n"
                        "1. A concise summary (3-5 sentences)\n"
                        "2. Key decisions made\n"
                        "3. Action items with owners (use speaker labels)\n\n"
                        f"Transcript:\n{speaker_transcript}"
                    ),
                }
            ],
            "max_tokens": 2000,
        },
    )
    llm_response.raise_for_status()

    print("=== Meeting Notes ===\n")
    print(llm_response.json()["choices"][0]["message"]["content"])
    ```
  </Tab>

  <Tab language="javascript" title="JavaScript">
    ```javascript expandable theme={null}
    const baseUrl = "https://api.assemblyai.com";
    const headers = {
      authorization: "YOUR_API_KEY",
      "Content-Type": "application/json",
    };

    const audioUrl = "https://assembly.ai/wildfires.mp3";

    // Step 1: Transcribe with speaker labels + language detection
    let res = await fetch(`${baseUrl}/v2/transcript`, {
      method: "POST",
      headers,
      body: JSON.stringify({
        audio_url: audioUrl,
        speech_models: ["universal-3-pro", "universal-2"],
        language_detection: true,
        speaker_labels: true,
      }),
    });
    if (!res.ok) throw new Error(`Error: ${res.status}`);
    const { id: transcriptId } = await res.json();

    let result;
    while (true) {
      res = await fetch(`${baseUrl}/v2/transcript/${transcriptId}`, { headers });
      result = await res.json();
      if (result.status === "completed") break;
      if (result.status === "error")
        throw new Error(`Transcription failed: ${result.error}`);
      await new Promise((r) => setTimeout(r, 3000));
    }

    // Step 2: Identify speakers by name
    const understandingRes = await fetch(
      "https://llm-gateway.assemblyai.com/v1/understanding",
      {
        method: "POST",
        headers,
        body: JSON.stringify({
          transcript_id: transcriptId,
          speech_understanding: {
            request: {
              speaker_identification: {
                speaker_type: "name",
                known_values: ["Alice", "Bob"], // Replace with actual participant names
              },
            },
          },
        }),
      }
    );
    if (!understandingRes.ok) throw new Error(`Error: ${understandingRes.status}`);
    const identified = await understandingRes.json();

    // Step 3: Format identified transcript for the LLM
    const speakerTranscript = identified.utterances
      .map((u) => `${u.speaker}: ${u.text}`)
      .join("\n");

    // Step 4: Generate meeting notes via LLM Gateway
    res = await fetch("https://llm-gateway.assemblyai.com/v1/chat/completions", {
      method: "POST",
      headers,
      body: JSON.stringify({
        model: "claude-sonnet-4-5-20250929",
        messages: [
          {
            role: "user",
            content:
              "You are a meeting notes assistant. Given the transcript below, produce:\n" +
              "1. A concise summary (3-5 sentences)\n" +
              "2. Key decisions made\n" +
              "3. Action items with owners (use speaker labels)\n\n" +
              `Transcript:\n${speakerTranscript}`,
          },
        ],
        max_tokens: 2000,
      }),
    });
    if (!res.ok) throw new Error(`Error: ${res.status}`);
    const llmResult = await res.json();

    console.log("=== Meeting Notes ===\n");
    console.log(llmResult.choices[0].message.content);
    ```
  </Tab>
</Tabs>

<Accordion title="Example output">
  ```text theme={null}
  === Meeting Notes ===

  ## Summary
  The discussion covered the impact of Canadian wildfire smoke on US air quality.
  Experts explained how particulate matter affects respiratory and cardiovascular
  health. The group reviewed current air quality index readings and discussed
  protective measures for affected communities.

  ## Key decisions
  - Monitor AQI levels daily until smoke clears
  - Issue public health advisories for sensitive groups

  ## Action items
  - Alice: Compile daily AQI data for the affected regions
  - Bob: Draft public advisory messaging for distribution
  - Alice: Coordinate with local health departments on response protocols
  ```
</Accordion>

<Tip>
  Speaker Identification maps generic labels like "Speaker A" to real names. You can pass a list of `known_values` to guide identification, or omit it to let the model infer names from the conversation. Learn more in the [Speaker Identification guide](/speech-understanding/speaker-identification).
</Tip>

***

See the [End-to-end examples overview](/getting-started/end-to-end-examples) for all available pipelines.
