Migrating from ElevenLabs to Speech Revolutions
ElevenLabs' Scribe speech-to-text is a single synchronous multipart POST to /v1/speech-to-text. Word timestamps come back by default and diarize=true adds a speaker_id to each word. Speech Revolutions covers the same ground — transcript, per-word times, and speakers — and the SDK reduces it to one transcribe() call. This guide maps auth, upload, and the response shape (including a token quirk to watch for), and points to where Speech Revolutions measures ahead for Scribe migrators.
What changes, what doesn't
Your diarize flag ports as-is (Speech Revolutions accepts diarize as an alias for speaker_labels), and result.text maps directly. The main cleanup is dropping ElevenLabs' spacing word-array tokens — the Speech Revolutions result.words are already words only.
Authentication
ElevenLabs authenticates with an xi-api-key header. Speech Revolutions uses X-API-Key, read from the environment by the SDK.
| ElevenLabs | Speech Revolutions |
|---|---|
xi-api-key: ELEVENLABS_API_KEY | X-API-Key: SPEECHREVOLUTIONS_API_KEY |
ELEVENLABS_API_KEY env var | SPEECHREVOLUTIONS_API_KEY |
Endpoint & method mapping
| ElevenLabs | Speech Revolutions REST | Speech Revolutions SDK |
|---|---|---|
POST /v1/speech-to-text (sync multipart, model_id) | POST /api/v1/upload → PUT to presigned URL → POST /api/v1/upload/complete | transcribe() (blocks until done) |
| — (response is inline) | GET /api/v1/jobs/{id} / /stream | get_transcript() / on_progress |
Upload differences
ElevenLabs takes the audio as a multipart file field with model_id in the form body. Speech Revolutions uploads through a presigned object-storage URL, so bytes stream to storage rather than through the API request — handled by the SDK when you pass a path, URL, or bytes. Speech Revolutions also reports live upload and transcribe progress (see live progress).
Response shape
ElevenLabs returns text plus a words[] array in which entries have a type of word or spacing; the spacing entries are not real words. Speakers appear as speaker_id strings (e.g. speaker_0). The Speech Revolutions result.words contains words only, with matching string speaker labels.
| ElevenLabs field | Speech Revolutions |
|---|---|
text | result.text |
words[] where type == "word" (text, start, end, speaker_id) | result.words (text, start, end, speaker) — no spacing tokens |
words[] where type == "spacing" | — (dropped; you no longer filter these out) |
— (regroup by speaker_id yourself) | result.utterances (pre-grouped speaker turns) |
language_code | result.languages |
Retire the spacing filter
If your code skips word.type === "spacing" entries, you can delete that filter — result.words is already words only.
Diarization
ElevenLabs diarizes with diarize=true, adding a speaker_id per word. Speech Revolutions uses the same diarize flag (alias for speaker_labels, on by default) and also groups words into result.utterances, so you don't reconstruct turns from per-word ids. Diarization accuracy is a clear Speech Revolutions strength — it leads on every subset in our testing; see the benchmarks and the comparison table for the measured DER.
Timestamps
Both return per-word start/end times in seconds by default. Timestamp precision is another area Speech Revolutions measures well on for Scribe migrators — the benchmarks report the median word-boundary error side by side, so you can compare rather than take our word for it.
Language selection
ElevenLabs takes language_code. Speech Revolutions always auto-detects, including code-switching mid-file — there's no language parameter to set. result.languages is a list of {start, end, language} segments covering the whole file, and every word in result.words also carries a language.
Side by side
from elevenlabs.client import ElevenLabs
client = ElevenLabs() # ELEVENLABS_API_KEY
with open("meeting.mp3", "rb") as f:
resp = client.speech_to_text.convert(
model_id="scribe_v2",
file=f,
diarize=True,
)
print(resp.text)
for w in resp.words:
if w.type == "word": # skip spacing tokens
print(w.speaker_id, w.text, w.start, w.end)Common pitfalls
- Auth header.
xi-api-key→X-API-Key. - Spacing tokens. The Speech Revolutions
wordsarray omits them — remove anytypefiltering. - Speaker grouping. Prefer
result.utterancesover regrouping per-wordspeaker_idvalues. - No keyword API on ElevenLabs. If you needed domain-term biasing and couldn't get it, Speech Revolutions adds
custom_vocabulary.
Next steps
See the migration playbook for cutover, and the benchmarks for diarization and timestamp comparisons.