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
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
Recommended flow: PUT /v1/note/audio
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
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
{
"noteId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"status_url": "/v1/audio/status/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}JavaScript (Node.js)
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
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.
curl https://api.soapnoteapi.com/v1/audio/status/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: Bearer YOUR_API_KEY"Polling response (processing)
{
"noteId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing"
}Polling response (completed)
{
"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
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
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.
{
"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"
}
}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.
// 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.
Need help? Contact support@soapnoteapi.com