⚡ ElixirASR Developer Guide
ElixirASR is a medical audio intelligence API. Upload recordings, get back structured transcripts with speaker diarization, and generate clinical notes via LLM templates — all from a single API call.
Async vs Sync — Which to Use?
ElixirASR has three transcription modes. Pick the right one for your use case.
Upload File
→
Get job_id
→
Poll Status
→
Download Result
| Mode | Endpoint | Best For | Limit |
|---|---|---|---|
| Async Batch | POST /process-audio/ | Long recordings, diarization + LLM, production pipelines | No hard limit |
| Sync | POST /transcribe | Short clips (<15 min), quick transcript + optional LLM prompt, no polling needed | 15 minutes audio |
| Live WebSocket | WS /ws/transcribe | Real-time recording from browser/app, streaming partial results | Session-based |
Batch Processing
Submit an audio file for full async processing — transcription, speaker diarization, and LLM output.
POST
/process-audio/
Submit audio for async processing → returns job_id
When to use this
Long recordings, clinical sessions, production pipelines. Returns a job_id immediately — poll for results.
Request Body — multipart/form-data
| Field | Type | Description | |
|---|---|---|---|
| audio_file | file | required | Audio file. Accepted: MP3 · WAV · M4A · WebM · OGG · FLAC · AAC · OPUS |
| num_speakers | integer | required | Number of distinct speakers in the recording (1–20) |
| output_template | string | required | Template filename (e.g. soap_note.json). Use GET /list-templates/ to see available options. |
| client_name | string | optional | Client identifier. Default: mirketa. Organises results under ElixirASR/{client_name}/ in Cloud Storage. |
| beam_size | integer | optional | Whisper beam width. Default: 3. Range 1–10. Lower = faster, higher = more accurate. |
| vad_filter | boolean | optional | Pre-filter silence before transcription. Default: true. Speeds up by 20–40% with no quality loss. |
| enable_diarization | boolean | optional | Enable speaker diarization (GPU only). Default: true. Set false for plain transcript. |
| voice_samples | file[] | optional | Reference audio clips, one per named speaker. Used for speaker identity matching. |
| voice_sample_names | string[] | optional | Names matching each voice sample file (e.g. "Dr. Smith", "Patient"). |
import requests BASE = "https://your-elixirasr-host" with open("consultation.mp3", "rb") as f: resp = requests.post( f"{BASE}/process-audio/", data={ "num_speakers": 2, "output_template": "soap_note.json", "client_name": "acme_clinic", }, files={"audio_file": ("consultation.mp3", f, "audio/mpeg")}, ) job = resp.json() job_id = job["job_id"] print(f"Queued: {job_id}")
const submitAudio = async (file) => { const form = new FormData(); form.append("audio_file", file); form.append("num_speakers", "2"); form.append("output_template", "soap_note.json"); form.append("client_name", "acme_clinic"); const resp = await fetch("/process-audio/", { method: "POST", body: form, }); const { job_id } = await resp.json(); console.log("Queued:", job_id); return job_id; };
curl -X POST https://your-elixirasr-host/process-audio/ \ -F "audio_file=@consultation.mp3" \ -F "num_speakers=2" \ -F "output_template=soap_note.json" \ -F "client_name=acme_clinic"
Response — 202 Accepted
{
"job_id": "a3f9b2c1-7e4d-4a8f-b1c2-9d0e5f6a7b8c",
"status": "queued",
"message": "Job accepted. Poll GET /jobs/{job_id} for status."
}
GET
/jobs/{job_id}
Poll job status
Poll this endpoint after submitting to /process-audio/. Status transitions: queued → processing → completed | failed. Recommended poll interval: 3–5 seconds.
import time def wait_for_job(job_id): while True: r = requests.get(f"{BASE}/jobs/{job_id}") job = r.json() if job["status"] == "completed": return job if job["status"] == "failed": raise RuntimeError(job["error"]) print(f"Status: {job['status']}") time.sleep(3)
async function pollJob(jobId) { while (true) { const resp = await fetch(`/jobs/${jobId}`); const job = await resp.json(); if (job.status === "completed") return job; if (job.status === "failed") throw new Error(job.error); await new Promise(r => setTimeout(r, 3000)); } }
curl https://your-elixirasr-host/jobs/a3f9b2c1-...
Response
{
"job_id": "a3f9b2c1-...",
"status": "completed",
"created_at": "2025-07-09T10:22:01Z",
"started_at": "2025-07-09T10:22:02Z",
"completed_at": "2025-07-09T10:22:45Z",
"result_blob_path": "ElixirASR/acme_clinic/Results/upload_xxx.json"
}
GET
/jobs/{job_id}/result
Download full result JSON
Returns the full result once the job is completed. Contains the transcript array, speaker labels, and the LLM-generated structured output.
Response
{
"job_id": "a3f9b2c1-...",
"transcript": [
{ "start": 0.0, "end": 4.2, "text": "Good morning, how are you feeling today?", "speaker": "SPEAKER_00" },
{ "start": 4.5, "end": 9.1, "text": "I've had a headache for three days.", "speaker": "SPEAKER_01" }
],
"custom_output": {
"Subjective": { "Chief Complaint": "Headache for 3 days", "HPI": "..." },
"Objective": { ... },
"Assessment": "...",
"Plan": "..."
}
}
GET
/jobs/{job_id}/logs/stream
Live log stream via SSE
Server-Sent Events stream. Opens a live log view of the job's pipeline steps. Each message is a JSON-encoded log line. A final done event fires when the job completes or fails.
const es = new EventSource(`/jobs/${jobId}/logs/stream`); es.onmessage = e => { console.log(JSON.parse(e.data)); // log line string }; es.addEventListener("done", e => { console.log("Finished:", JSON.parse(e.data)); // "completed" | "failed" es.close(); });
import sseclient, requests url = f"{BASE}/jobs/{job_id}/logs/stream" with requests.get(url, stream=True) as r: client = sseclient.SSEClient(r) for event in client.events(): if event.event == "done": break print(event.data)
Synchronous Transcription
Returns the full transcript in a single blocking response — no polling. A job is created automatically for audit purposes and is visible in the Job Monitor.
POST
/transcribe
Transcribe with optional LLM prompt — one-shot, 15 min limit
When to use this
Audio clips under 15 minutes where you want results immediately. Prompt is optional — omit it for a plain transcript with no LLM call.
⚠️ Hard limit: 15 minutes audio. For longer recordings use POST /process-audio/ instead.
Request Body — multipart/form-data
| Field | Type | Description | |
|---|---|---|---|
| audio_file | file | required | Audio file ≤ 15 minutes |
| prompt | string | optional | LLM prompt. Use {transcript} as placeholder. Omit to skip LLM and return transcript only. |
| timestamped | boolean | optional | Default: true. When true, transcript is a list of segments with start/end times. When false, returns a single plain string. |
| client_name | string | optional | Client identifier. Default: mirketa. Used for Cloud Storage result path and Job Monitor grouping. |
| model | string | optional | Whisper model. Allowed: tiny.en, base.en, small.en, small. Default: base.en |
| language | string | optional | Force language detection. E.g. en, fr. Leave empty for auto-detect. |
| beam_size | integer | optional | Beam width 1–10. Default: 3 |
| vad_filter | boolean | optional | Silence filtering. Default: true |
# Plain transcript (no LLM) with open("clip.mp3", "rb") as f: resp = requests.post( f"{BASE}/transcribe", data={"model": "base.en", "timestamped": "true"}, files={"audio_file": ("clip.mp3", f, "audio/mpeg")}, ) result = resp.json() print(result["job_id"]) # audit trail in Job Monitor print(result["transcript"]) # list of {start, end, text} segments # With LLM prompt PROMPT = """Summarise the following consultation in 3 bullet points: {transcript}""" with open("clip.mp3", "rb") as f: resp = requests.post( f"{BASE}/transcribe", data={"prompt": PROMPT, "model": "base.en"}, files={"audio_file": ("clip.mp3", f, "audio/mpeg")}, ) result = resp.json() print(result["prompt_output"]) # structured JSON from LLM
const form = new FormData(); form.append("audio_file", audioBlob, "clip.mp3"); form.append("model", "base.en"); // optionally add prompt: // form.append("prompt", "Summarise: {transcript}"); const resp = await fetch("/transcribe", { method: "POST", body: form }); const { job_id, transcript, prompt_output } = await resp.json(); console.log("Job:", job_id); console.log("Transcript:", transcript);
# Plain transcript curl -X POST https://your-elixirasr-host/transcribe \ -F "audio_file=@clip.mp3" \ -F "model=base.en" # With LLM prompt curl -X POST https://your-elixirasr-host/transcribe \ -F "audio_file=@clip.mp3" \ -F 'prompt=Summarise the consultation: {transcript}' \ -F "model=base.en"
Response
{
"job_id": "b7e2f1a3-...",
"model_used": "base.en",
"language_detected": "en",
"duration_seconds": 312.4,
"diarization_enabled": false,
"transcript": [
{ "start": 0.0, "end": 3.8, "text": "Good morning, how are you feeling today?" },
{ "start": 4.1, "end": 8.5, "text": "I've had a headache for three days." }
],
"prompt_output": null
}
Live WebSocket Transcription
Stream raw audio bytes from the browser microphone and receive partial transcripts in real time.
WS
/ws/transcribe
Real-time streaming transcription
When to use this
Browser-based recording apps, voice interfaces, real-time note-taking. The server streams partial transcripts back as you speak, then sends the full result when you stop.
💡 Partial transcripts arrive roughly every LIVE_PARTIAL_INTERVAL_SECONDS (default 2s) and each one is a fresh re-transcription of only the last LIVE_PARTIAL_WINDOW_SECONDS (default 8s) of audio — a rolling window, not a running total. Expect recent words to restate or slightly change as more audio arrives; don't concatenate partial messages. The single transcript message sent after the stop sentinel covers the complete session at full quality and is the authoritative result.
Query Parameters
| Param | Type | Description | |
|---|---|---|---|
| num_speakers | integer | optional | Default: 2 |
| output_template | string | optional | Template filename. Default: soap_note.json |
| job_id | string | optional | UUID for the session. Auto-generated if omitted. |
| client_name | string | optional | Client identifier. Default: default |
// Connect — auto-select wss:// on HTTPS, ws:// on HTTP const proto = location.protocol === "https:" ? "wss:" : "ws:"; const ws = new WebSocket( `${proto}//${location.host}/ws/transcribe?num_speakers=2&output_template=soap_note.json` ); ws.binaryType = "arraybuffer"; // Stream microphone audio const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const recorder = new MediaRecorder(stream, { mimeType: "audio/webm;codecs=opus" }); recorder.ondataavailable = e => { if (e.data.size > 0) e.data.arrayBuffer().then(buf => ws.send(buf)); }; recorder.start(250); // 250ms chunks // Receive partial / final results ws.onmessage = e => { const data = JSON.parse(e.data); if (data.partial) console.log("Partial:", data.partial); if (data.transcript) console.log("Final:", data); // full result }; // Stop: send 1-byte sentinel, then wait for final result const stop = () => { recorder.onstop = () => ws.send(new Uint8Array(1)); recorder.stop(); };
Templates
Templates are JSON files that define the LLM prompt and output structure for a specific use case (SOAP notes, referral letters, summaries, etc.).
GET
/list-templates/
Returns a list of template filenames available in Cloud Storage.
templates = requests.get(f"{BASE}/list-templates/").json() # ["soap_note.json", "referral_letter.json", ...]
curl https://your-elixirasr-host/list-templates/
POST
/add-template/
Upload a new template or overwrite an existing one. The filename is derived from the name field.
💡 Use {transcript} in your prompt — it's replaced with the full transcript text before the LLM call.
requests.post(f"{BASE}/add-template/", json={ "name": "referral_letter", "prompt": "Write a specialist referral letter based on:\n\n{transcript}", })
curl -X POST https://your-elixirasr-host/add-template/ \
-H "Content-Type: application/json" \
-d '{"name":"referral_letter","prompt":"Write a referral letter for:\n{transcript}"}'
Results & Client Management
GET
/list-results/{client_name}
Lists all result filenames for a client from Cloud Storage. Omit client_name to use GET /list-results/ (defaults to mirketa).
files = requests.get(f"{BASE}/list-results/acme_clinic").json() # ["upload_abc123.json", "upload_def456.json"]
POST
/setup-client-storage/{client_name}
Provisions folder structure for a new client in Cloud Storage. Safe to call multiple times — idempotent. Creates: Uploads/, Results/, templates/.
requests.post(f"{BASE}/setup-client-storage/acme_clinic")
GET
/version
Returns the running image version and runtime configuration.
Response
{
"version": "1.6.0",
"batch_model": "base",
"transcribe_model": "base.en",
"live_model": "base.en",
"gpu": false,
"beam_size": 3,
"vad_filter": true,
"concurrency": 3
}