SOAPNoteAPI

Streaming & Webhooks

Stream Notes in Real Time

Use Server-Sent Events to display SOAP note sections as they are generated, reducing perceived latency for providers.

Updated March 18, 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.

Why streaming matters

Synchronous note generation takes 10-30 seconds depending on transcript length and specialty complexity. For user-facing applications, that is an eternity of dead spinner. Streaming lets you display the note as it is being generated -- providers see the Subjective section appear within 1-2 seconds, followed by Objective, Assessment, and Plan. This dramatically improves the perceived speed and gives providers confidence that the system is working.

SSE connection setup

The streaming endpoint is POST /v1/stream/note. It accepts the same request body as POST /v1/note but returns a Server-Sent Events (SSE) stream instead of a single JSON response. Set Accept: text/event-stream in your request headers.

cURL

Terminal
curl -X POST https://api.soapnoteapi.com/v1/stream/note \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
    "transcript": "Patient is a 52-year-old male presenting with lower back pain for the past 3 weeks. Pain is rated 6/10, worsens with sitting, improves with walking. No radiculopathy. No bowel or bladder changes. Taking ibuprofen 400mg PRN with partial relief.",
    "specialty": "primary_care_physician"
  }'

Event format

The stream emits SSE events. Each event has a data field containing a JSON object with a content string (the next chunk of generated text). The stream ends with a data: [DONE] sentinel.

Terminal
data: {"content":"Subjective:\n"}

data: {"content":"52-year-old male presents with "}

data: {"content":"lower back pain of three weeks duration. "}

data: {"content":"Pain rated 6/10..."}

...

data: {"content":"\n\nPlan:\n1. Continue ibuprofen..."}

data: [DONE]

Browser EventSource example

Note: The native EventSource API only supports GET requests. Since the streaming endpoint is POST, you need to use a library like eventsource-parser or implement manual SSE parsing with fetch. Here is the fetch-based approach:

JavaScript
async function streamNote(transcript, specialty) {
  const response = await fetch("https://api.soapnoteapi.com/v1/stream/note", {
    method: "POST",
    headers: {
      "Authorization": "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
      "Accept": "text/event-stream",
    },
    body: JSON.stringify({ transcript, specialty }),
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let noteText = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value, { stream: true });
    const lines = chunk.split("\n");

    for (const line of lines) {
      if (line.startsWith("data: ")) {
        const data = line.slice(6);
        if (data === "[DONE]") return noteText;

        try {
          const parsed = JSON.parse(data);
          noteText += parsed.content;
          // Update your UI here:
          document.getElementById("note-output").textContent = noteText;
        } catch {
          // Skip malformed lines
        }
      }
    }
  }

  return noteText;
}

Node.js manual parsing

JavaScript
import { createParser } from "eventsource-parser";

const response = await fetch("https://api.soapnoteapi.com/v1/stream/note", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "text/event-stream",
  },
  body: JSON.stringify({
    transcript: "Patient presents with lower back pain...",
    specialty: "primary_care_physician",
  }),
});

let fullNote = "";

const parser = createParser((event) => {
  if (event.type === "event") {
    if (event.data === "[DONE]") {
      console.log("\n--- Stream complete ---");
      console.log(fullNote);
      return;
    }
    const { content } = JSON.parse(event.data);
    fullNote += content;
    process.stdout.write(content);
  }
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  parser.feed(decoder.decode(value, { stream: true }));
}

Python SSE client

Python
import requests
import json

response = requests.post(
    "https://api.soapnoteapi.com/v1/stream/note",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
        "Content-Type": "application/json",
        "Accept": "text/event-stream",
    },
    json={
        "transcript": "Patient presents with lower back pain...",
        "specialty": "primary_care_physician",
    },
    stream=True,
)

full_note = ""

for line in response.iter_lines(decode_unicode=True):
    if line.startswith("data: "):
        data = line[6:]
        if data == "[DONE]":
            break
        try:
            parsed = json.loads(data)
            full_note += parsed["content"]
            print(parsed["content"], end="", flush=True)
        except json.JSONDecodeError:
            pass

print("\n--- Stream complete ---")
print(full_note)

Error handling and reconnection

  • If the connection drops mid-stream, you will receive a network error. Retry the full request -- partial results are not resumable.
  • A 429 response means your account has insufficient balance. Add funds at your dashboard and try again.
  • If you receive an error event in the stream (data: {"error": "..."}), the generation has failed. Display a user-friendly message and let the provider try again.

When to use streaming vs. synchronous

  • User-facing applications: Always use streaming. The perceived speed improvement is significant for provider satisfaction.
  • Batch processing / backend pipelines: Use the synchronous POST /v1/note endpoint. Simpler to implement and easier to handle errors.
  • Audio processing: Audio notes use the polling pattern (PUT /v1/note/audio then poll GET /v1/audio/status/:noteId), not streaming. Streaming is only available for text-to-note generation.

Need help? Contact support@soapnoteapi.com