Documentation
Everything on this page is live behavior of the MedSpeech API and the echo-1 model: batch and realtime speech-to-text, the stability contract, errors, quotas, and the Python SDK.
1. Quickstart
Authentication
Every request carries your API key in the xi-api-key header. For WebSocket connections, pass it as the ?xi-api-key= query parameter instead. Keys are shown once at creation and stored only as a SHA-256 hash.
Transcribe a file
curl -H "xi-api-key: $KEY" \ -F file=@visit.wav -F model=echo-1 \ https://medspeech.io/api/v1/speech-to-text
Or with the Python SDK
pip install medasr
from medasr.client import MedASRClient
client = MedASRClient("https://medspeech.io/api", api_key="...")
result = client.transcribe("visit.wav", model="echo-1")
print(result.text)2. Models
| Model | Domain | Modes |
|---|---|---|
echo-1 | English medical dictation | Streaming + batch |
Select the model with model=, or with the ElevenLabs-style model_id=. If both are present they must agree, otherwise the request fails with 400. If omitted, the default model is used.
The name medasr is accepted as a deprecated alias for echo-1. Unknown model names return 400 invalid_model_id.
3. Batch transcription
POST /v1/speech-to-text — multipart form upload. Transcribes recordings of any length: offline VAD segmentation, one exact full-context decoding pass per speech region, absolute file timestamps on every word.
Request fields
| Field | Values | Notes |
|---|---|---|
file | audio file | Required. Any length. |
model / model_id | echo-1 | Must agree if both given. |
timestamps_granularity | word · none | Default word. |
use_multi_channel | true · false | Each channel transcribed independently. |
multichannel_output_style | separate · combined | Per-channel transcripts, or time-merged. |
language_code | en | English only. |
Response
{
"language_code": "en",
"language_probability": 1.0,
"text": "Patient denies chest pain or palpitations.",
"words": [
{ "text": "Patient", "start": 0.32, "end": 0.61,
"type": "word", "logprob": -0.04 },
{ "text": "denies", "start": 0.66, "end": 0.98,
"type": "word", "logprob": -0.02 }
]
}With use_multi_channel=true and multichannel_output_style=separate, the response is a list of per-channel transcripts:
{
"transcripts": [
{ "channel_index": 0, "speaker_id": "speaker_0",
"text": "...", "words": [ ... ] },
{ "channel_index": 1, "speaker_id": "speaker_1",
"text": "...", "words": [ ... ] }
]
}Limits
- Maximum 5 channels per file.
- English only (
language_code=en). - No
diarizeoption — speaker separation is channel-based; useuse_multi_channel.
4. Realtime
WS /v1/speech-to-text/realtime — stream audio in, receive partial transcripts every ~240 ms and a committed transcript at each end of utterance.
Query parameters
| Param | Values |
|---|---|
model | echo-1 |
sample_rate | e.g. 16000 |
audio_format | pcm_16000-style format strings |
commit_strategy | vad (default) · manual |
xi-api-key | your API key |
Client frames
// audio, as JSON…
{ "type": "audio", "audio_base64": "<base64 PCM16>" }
// …or as a raw binary frame of PCM16 bytes
// end the current utterance (commit_strategy=manual)
{ "type": "commit" }
// end the session
{ "type": "end" }Server events
{ "type": "session_started", ... }
{ "type": "partial_transcript",
"text": "patient denies chest pain or",
"stable_text": "patient denies chest",
"words": [ ... ] }
{ "type": "committed_transcript",
"text": "Patient denies chest pain or palpitations." }
{ "type": "committed_transcript_with_timestamps",
"words": [ { "text": "Patient", "start": 0.32, ... } ] }The stability contract
Normative: in every partial_transcript, stable_text is monotone and content-frozen. A later partial may only ever extend it — no word in stable_text is ever edited or retracted. Text beyond stable_text is tentative and may change. The committed_transcript for an utterance is decoded fresh over the full utterance audio and is authoritative.
End of utterance
With commit_strategy=vad, an utterance ends only when the endpoint detects both trailing silence (VAD) and a run of confident CTC-blank frames — quiet-but-voiced speech is not cut off mid-sentence. With commit_strategy=manual, the client decides: send {"type":"commit"}.
Close codes
| Code | Meaning |
|---|---|
1008 | Authentication failed or bad model. |
1013 | Over quota — sent after an error frame with code rate_limited or concurrency_exceeded. |
5. Errors
Every 4xx error has exactly one machine-readable shape:
{
"detail": {
"status": "rate_limited",
"message": "Too many requests. Retry after 2 seconds."
}
}| Status slug | HTTP | Meaning |
|---|---|---|
invalid_api_key | 401 | Missing, malformed, or revoked key. |
account_suspended | 403 | The account is suspended. |
invalid_model_id | 400 | Unknown model, or conflicting model/model_id. |
invalid_audio_file | 400 | The file could not be decoded as audio. |
rate_limited | 429 | Request rate over quota. |
concurrency_exceeded | 429 | Too many concurrent requests or streams. |
All 429 responses carry a Retry-After header (seconds).
6. Quotas & rate limits
Every account starts with the default grant:
| Surface | Default quota |
|---|---|
| Streaming | 5 new sessions/s · 5 concurrent streams |
| Batch | 5 requests/s (burst 10) · 5 concurrent |
Over quota, HTTP requests receive 429 with Retry-After; realtime sessions receive an error frame (rate_limited or concurrency_exceeded) followed by close code 1013. Rejected requests are never billed.
Quotas are per-customer and raised on request — email info@medspeech.io with your expected volume.
7. Python SDK
The medasr package is torch-free — it’s a thin client over the HTTP and WebSocket APIs.
from medasr.client import MedASRClient
client = MedASRClient("https://medspeech.io/api", api_key="...")
# Batch
result = client.transcribe("visit.wav", model="echo-1")
for w in result.words:
print(w.text, w.start, w.end, w.logprob)
# Realtime
with client.realtime(
on_partial=lambda p: render(p.stable_text, p.text),
on_committed=lambda c: append_to_note(c.text),
) as session:
for chunk in mic_chunks():
session.send_audio(chunk)Typed exceptions
from medasr.client import (
AuthenticationError, # invalid or revoked key
RateLimitError, # has .retry_after (seconds)
InvalidRequestError, # bad model, bad audio, bad params
)
try:
result = client.transcribe("visit.wav")
except RateLimitError as e:
time.sleep(e.retry_after)8. Migrating from ElevenLabs
The wire format is ElevenLabs-compatible. Point your existing client at https://medspeech.io/api and it works.
Identical
- Auth header:
xi-api-key. - Field names and WebSocket event names.
- Response shapes, including
words[{text, start, end, type, logprob}].
Documented divergences
- No
diarizeoption. Speaker separation is channel-based: useuse_multi_channel=true. - No character-level timestamps.
- No
spacingword entries — every entry is a word. language_probabilityis constant1.0(English-only model).
9. Security & compliance
PHI lifecycle
Audio and transcripts exist only in memory, for the duration of the request that carries them. They are never written to disk, never logged, and never included in billing or metering events. Logs and usage records contain request ids, audio durations, and model names only.
API keys
Keys are stored as SHA-256 hashes; the raw key is shown once at creation and cannot be recovered. Keys can be revoked immediately, and accounts can be suspended.
Data residency
The default endpoint, https://medspeech.io/api, makes no residency promise. Customers with data-residency requirements connect to a region-pinned host — https://<region>.api.medspeech.io, e.g. https://eu-west-1.api.medspeech.io. The hostname is the residency mechanism: audio sent to a regional host is processed in that region and PHI never leaves it. Only non-PHI customer and billing metadata crosses regions.
Platform posture
HIPAA-track architecture: encrypted logs, a full audit trail, and an admin plane unreachable from the public internet. A BAA is available for design partners — contact info@medspeech.io to discuss your timeline.
MedSpeech produces draft transcription for clinician review. It is not a medical device and does not provide medical advice. Validate output before clinical use.