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
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

python
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

ModelDomainModes
echo-1English medical dictationStreaming + 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

FieldValuesNotes
fileaudio fileRequired. Any length.
model / model_idecho-1Must agree if both given.
timestamps_granularityword · noneDefault word.
use_multi_channeltrue · falseEach channel transcribed independently.
multichannel_output_styleseparate · combinedPer-channel transcripts, or time-merged.
language_codeenEnglish only.

Response

200 · single channel
{
  "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:

200 · multichannel, separate
{
  "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 diarize option — speaker separation is channel-based; use use_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

ParamValues
modelecho-1
sample_ratee.g. 16000
audio_formatpcm_16000-style format strings
commit_strategyvad (default) · manual
xi-api-keyyour API key

Client frames

client → server
// 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

server → client
{ "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

CodeMeaning
1008Authentication failed or bad model.
1013Over quota — sent after an error frame with code rate_limited or concurrency_exceeded.

5. Errors

Every 4xx error has exactly one machine-readable shape:

error shape
{
  "detail": {
    "status": "rate_limited",
    "message": "Too many requests. Retry after 2 seconds."
  }
}
Status slugHTTPMeaning
invalid_api_key401Missing, malformed, or revoked key.
account_suspended403The account is suspended.
invalid_model_id400Unknown model, or conflicting model/model_id.
invalid_audio_file400The file could not be decoded as audio.
rate_limited429Request rate over quota.
concurrency_exceeded429Too 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:

SurfaceDefault quota
Streaming5 new sessions/s · 5 concurrent streams
Batch5 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.

batch + realtime
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

error handling
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 diarize option. Speaker separation is channel-based: use use_multi_channel=true.
  • No character-level timestamps.
  • No spacing word entries — every entry is a word.
  • language_probability is constant 1.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.