Skip to main content
The acronym SOAP stands for Subjective, Objective, Assessment, and Plan. This standardized method of documenting patient encounters allows providers to concisely record patient information. This guide walks through how to generate SOAP notes with AssemblyAI’s LLM Gateway, which provides access to multiple LLM providers through a unified API.

Get Started

Before we begin, make sure you have an AssemblyAI account and an API key. You can sign up for an AssemblyAI account and get your API key from your dashboard. You will need to upgrade your account by adding a credit card to have access to LLM Gateway. Find more details on the current pricing in the AssemblyAI pricing page.

Step-by-Step Instructions

Install the required packages:
pip install requests
Set up your transcription with PII redaction enabled to protect patient information:
import requests
import time

base_url = "https://api.assemblyai.com"

headers = {
    "authorization": "<YOUR_API_KEY>"
}

with open("./local_file.mp3", "rb") as f:
    response = requests.post(base_url + "/v2/upload",
                            headers=headers,
                            data=f)

upload_url = response.json()["upload_url"]

data = {
    "audio_url": upload_url, # You can also use a URL to an audio or video file on the web
    "speech_models": ["universal-3-pro"],
    "redact_pii": True,
    "redact_pii_policies": ["person_name", "organization", "occupation"],
    "redact_pii_sub": "hash"
}

url = base_url + "/v2/transcript"
response = requests.post(url, json=data, headers=headers)

transcript_id = response.json()['id']
polling_endpoint = base_url + "/v2/transcript/" + transcript_id

print(f"Transcript ID: {transcript_id}")

while True:
    transcription_result = requests.get(polling_endpoint, headers=headers).json()

    if transcription_result['status'] == 'completed':
        print(transcription_result['text'])
        break
    elif transcription_result['status'] == 'error':
        raise RuntimeError(f"Transcription failed: {transcription_result['error']}")
    else:
        time.sleep(3)

Two different methods of generating SOAP notes

Method 1: Complete SOAP Note Generation

This method uses LLM Gateway to generate the entire SOAP note in one request. The prompt includes context about the audio file, a specific format for the SOAP note, and detailed instructions for each section.
prompt = """
Generate a SOAP note summary based on the following doctor-patient appointment transcript.

Context: This is a doctor appointment between patient and provider. The personal identification information has been redacted.

Format the response as follows:

Subjective
This is typically the shortest section (only 2-3 sentences) and it describes the patient's affect, as the professional sees it. This information is all subjective (it isn't measureable).
- Include information that may have affected the patient's performance, such as if they were sick, tired, attentive, distractible, etc.
- Was the patient on time or did they come late?
- May include a quote of something the patient said, or how they reported feeling

Objective
This section includes factual, measurable, and objective information. This may include:
- Direct patient quotes
- Measurements
- Data on patient performance

Assessment
This section should be the meat of the SOAP note. It contains a narrative of what actually happened during the session. There may be information regarding:
- Whether improvements have been made since the last session
- Any potential barriers to success
- Clinician's interpretation of the results of the session

Plan
This is another short section that states the plan for future sessions. In most settings, this section may be bulleted
"""

# Send to LLM Gateway
llm_gateway_data = {
    "model": "claude-sonnet-4-5-20250929",
    "messages": [
        {"role": "user", "content": f"{prompt}\n\n{{{{ transcript }}}}"}
    ],
    "transcript_id": transcript_id,
    "max_tokens": 2000
}

response = requests.post(
    "https://llm-gateway.assemblyai.com/v1/chat/completions",
    headers=headers,
    json=llm_gateway_data
)

result = response.json()["choices"][0]["message"]["content"]
print(result.strip())

Method 2: Section-by-Section Generation

This method generates each section of the SOAP note separately using LLM Gateway. This makes it easy to regenerate one or more sections individually if needed. Each section is generated with a specific prompt tailored to that part of the SOAP note.
# Define questions for each SOAP section
questions = [
    {
        "section": "Subjective",
        "question": "What are the patient's current symptoms or concerns?",
        "context": "Gather information about the patient's subjective experience. Example: The patient reports experiencing persistent headaches and dizziness.",
        "format": "<patient's symptoms or concerns>, [exact quote from patient]"
    },
    {
        "section": "Objective",
        "question": "What are the measurable and observable findings from the examination?",
        "context": "Collect data on the patient's objective signs and measurements. Example: The examination reveals an elevated body temperature and increased heart rate.",
        "format": "<measurable and observable findings>"
    },
    {
        "section": "Assessment",
        "question": "Based on the patient's history and examination, what is your assessment or diagnosis?",
        "context": "Formulate a professional assessment based on the gathered information. Example: Based on the patient's symptoms, examination, and medical history, the preliminary diagnosis is migraine.",
        "format": "<assessment or diagnosis>"
    },
    {
        "section": "Plan",
        "question": "What is the plan of action or treatment for the patient?",
        "context": "Outline the intended course of action or treatment. Example: The treatment plan includes prescribing medication, recommending rest, and scheduling a follow-up appointment in two weeks.",
        "format": "<plan of action or treatment>, [exact quote from provider]"
    }
]

# Process each section
for q in questions:
    prompt = f"""
{q['question']}

Context: This is a doctor appointment between patient and provider. The personal identification information has been redacted. {q['context']}

Answer Format: {q['format']}
"""

    llm_gateway_data = {
        "model": "claude-sonnet-4-5-20250929",
        "messages": [
            {"role": "user", "content": f"{prompt}\n\n{{{{ transcript }}}}"}
        ],
        "transcript_id": transcript_id,
        "max_tokens": 1000
    }

    response = requests.post(
        "https://llm-gateway.assemblyai.com/v1/chat/completions",
        headers=headers,
        json=llm_gateway_data
    )

    result = response.json()["choices"][0]["message"]["content"]
    print(f"{q['section']}: {q['question']}")
    print(result.strip())
    print()