SOAPNoteAPI

Audio Processing

Generate Notes from Audio Recordings

Upload a clinical audio recording and receive a structured SOAP note. One API call. SOAPNoteAPI handles transcription and generation end-to-end.

Updated March 19, 2026

Note: Clinical accuracy disclaimer: All generated notes must be reviewed by a licensed healthcare provider before use in patient care. Example outputs in this guide are illustrative.

How audio-to-note works

SOAPNoteAPI handles the full pipeline: you upload an audio file, SOAPNoteAPI transcribes it using Whisper, then generates a structured SOAP note from the transcript. One API call. That is it. Then poll for the result or receive it via webhook.

Two upload flows

Upload audio to SOAPNoteAPI using PUT /v1/note/audio. Send the audio file and metadata in a single multipart/form-data request. One API call handles upload, transcription trigger, and returns a noteId for tracking.

Supported formats and limits

  • Formats: MP3, M4A, WAV, OGG, WEBM, FLAC, MP4
  • Max file size: 500 MB
  • Max duration: 4 hours
  • Minimum duration: 5 seconds
  • Sample rate: 16 kHz or higher recommended for best transcription accuracy

Send a multipart/form-data request with two fields: audio (the file) and metadata (a JSON string with specialty, template, context, and options). The response returns immediately with a noteId and status URL for polling.

Metadata fields

  • specialty (required) -- The medical specialty for note formatting (e.g., "nurse_practitioner", "psychiatrist").
  • template (optional) -- A sub-template within the specialty. Defaults to the specialty standard template.
  • context (optional) -- Clinical context object (patient_info, patient_history, chat_messages, custom_instructions).
  • include_billing_codes (optional, boolean) -- Request ICD-10 and CPT code suggestions.
  • include_patient_summary (optional, boolean) -- Request a plain-language patient visit summary.

cURL

Terminal
curl -X PUT https://api.soapnoteapi.com/v1/note/audio \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F 'audio=@encounter-recording.mp3' \
  -F 'metadata={"specialty":"nurse_practitioner","include_billing_codes":true}'

Response

JSON
{
  "noteId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "processing",
  "status_url": "/v1/audio/status/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

JavaScript (Node.js)

JavaScript
import { readFile } from "fs/promises";

const audioBuffer = await readFile("encounter-recording.mp3");

const formData = new FormData();
formData.append("audio", new Blob([audioBuffer], { type: "audio/mpeg" }), "encounter-recording.mp3");
formData.append("metadata", JSON.stringify({
  specialty: "nurse_practitioner",
  include_billing_codes: true,
  context: {
    patient_info: { name: "Maria Santos", age: 62, gender: "female" },
  },
}));

const response = await fetch("https://api.soapnoteapi.com/v1/note/audio", {
  method: "PUT",
  headers: { "Authorization": "Bearer YOUR_API_KEY" },
  body: formData,
});

const { noteId, status_url } = await response.json();
console.log("Processing started. Note ID:", noteId);

Python

Python
import json
import requests

metadata = json.dumps({
    "specialty": "nurse_practitioner",
    "include_billing_codes": True,
    "context": {
        "patient_info": {"name": "Maria Santos", "age": 62, "gender": "female"},
    },
})

with open("encounter-recording.mp3", "rb") as f:
    response = requests.put(
        "https://api.soapnoteapi.com/v1/note/audio",
        headers={"Authorization": "Bearer YOUR_API_KEY"},
        files={
            "audio": ("encounter-recording.mp3", f, "audio/mpeg"),
            "metadata": (None, metadata, "application/json"),
        },
    )

data = response.json()
print(f"Processing started. Note ID: {data['noteId']}")

Getting the result: poll or webhook

After uploading, you have two ways to receive the completed note.

Option A: Poll for completion

Poll GET /v1/audio/status/:noteId until the status changes from "processing" to "completed" (or "failed"). We recommend polling every 3-5 seconds.

Terminal
curl https://api.soapnoteapi.com/v1/audio/status/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Authorization: Bearer YOUR_API_KEY"

Polling response (processing)

JSON
{
  "noteId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "processing"
}

Polling response (completed)

JSON
{
  "noteId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "completed",
  "note": {
    "noteId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "subjective": "Patient presents for follow-up...",
    "objective": "Vital signs: BP 130/84...",
    "assessment": "Hypertension, improving...",
    "plan": "1. Continue current medication...",
    "transcript": "The transcribed audio content...",
    "billing_codes": {
      "icd10": [{ "code": "I10", "description": "Essential hypertension" }],
      "cpt": [{ "code": "99214", "description": "Office visit, established patient" }]
    },
    "expires_at": "2026-03-20T15:30:00.000Z"
  }
}

JavaScript polling example

JavaScript
async function waitForNote(noteId) {
  const maxAttempts = 60; // 5 minutes at 5-second intervals
  for (let i = 0; i < maxAttempts; i++) {
    const response = await fetch(
      `https://api.soapnoteapi.com/v1/audio/status/${noteId}`,
      { headers: { "Authorization": "Bearer YOUR_API_KEY" } }
    );
    const data = await response.json();

    if (data.status === "completed") return data.note;
    if (data.status === "failed") throw new Error("Audio processing failed");

    await new Promise((r) => setTimeout(r, 5000)); // Wait 5 seconds
  }
  throw new Error("Timeout waiting for audio processing");
}

const note = await waitForNote(noteId);
console.log(note);

Python polling example

Python
import time
import requests

def wait_for_note(note_id, api_key, timeout=300, interval=5):
    elapsed = 0
    while elapsed < timeout:
        response = requests.get(
            f"https://api.soapnoteapi.com/v1/audio/status/{note_id}",
            headers={"Authorization": f"Bearer {api_key}"},
        )
        data = response.json()

        if data["status"] == "completed":
            return data["note"]
        if data["status"] == "failed":
            raise Exception("Audio processing failed")

        time.sleep(interval)
        elapsed += interval

    raise Exception("Timeout waiting for audio processing")

note = wait_for_note(note_id, "YOUR_API_KEY")
print(note)

Option B: Receive via webhook

For production integrations, webhooks eliminate polling entirely. Configure a webhook URL in your dashboard and SOAPNoteAPI will POST a signed event to your endpoint when audio processing completes or fails. The webhook delivers the full SOAP note -- no need to call GET /v1/note/:noteId separately.

Warning: Webhook payloads contain PHI, including the full clinical note. Ensure your receiving endpoint is HIPAA-compliant -- HTTPS, secure storage, and restricted access. See the Webhook Integration guide for details.
JSON
{
  "event": "note.generated",
  "note_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "completed",
  "created_at": "2026-03-19T15:30:00.000Z",
  "data": {
    "noteId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "subjective": "Patient presents with...",
    "objective": "Vital signs within normal limits...",
    "assessment": "Acute upper respiratory infection...",
    "plan": "1. Symptomatic treatment...",
    "transcript": "The transcribed audio content...",
    "billing_codes": {
      "icd10": [{ "code": "J06.9", "description": "Acute upper respiratory infection" }],
      "cpt": [{ "code": "99213", "description": "Office visit, established patient" }]
    },
    "patient_summary": "You visited your doctor today for cold symptoms...",
    "expires_at": "2026-03-20T15:30:00.000Z"
  }
}
Tip: The webhook delivers everything -- subjective, objective, assessment, plan, transcript, billing codes, patient summary, and expiration date. You do not need to make a separate GET request to retrieve the note.

SOAPNoteAPI signs each webhook with HMAC-SHA256. Verify the signature using the X-SoapNoteAPI-Signature and X-SoapNoteAPI-Timestamp headers to ensure the payload is authentic. See the Webhook Integration guide for full details.

Passing clinical context with audio

PUT /v1/note/audio supports clinical context. Include it in the metadata JSON field of the multipart request. The context enriches the generated note with patient demographics and clinical history.

JSON
// metadata field for PUT /v1/note/audio
{
  "specialty": "psychotherapist",
  "include_billing_codes": true,
  "context": {
    "patient_info": {
      "name": "Jane Doe",
      "age": 34,
      "gender": "female",
      "medical_history": "Generalized anxiety disorder, PTSD"
    },
    "patient_history": "Prior session focused on CBT techniques for anxiety management."
  }
}

Error handling

  • 400 VALIDATION_ERROR -- Missing required fields (specialty is required for both flows).
  • 401 AUTHENTICATION_ERROR -- Invalid or missing API key.
  • 429 INSUFFICIENT_BALANCE -- Insufficient balance. Add funds at your dashboard.
  • status: "failed" on polling -- Transcription or generation failed. Common causes: corrupted file, audio too short (< 5 seconds), audio with no speech detected, or missing specialty in metadata.
Tip: See the Webhook Integration guide for the full webhook setup, including dashboard configuration, secret rotation, signature verification in Python, retry policies, and testing with the built-in test event feature.

Need help? Contact support@soapnoteapi.com