Migrating from AssemblyAI to Speech Revolutions

AssemblyAI already uses an async flow: upload the file to /v2/upload, submit a job to /v2/transcript, then poll /v2/transcript/{id} until status === "completed". Speech Revolutions follows the same async model. The SDK runs all three steps in one blocking transcribe() call, or you can keep them separate with submit() and get_transcript().

Familiar ergonomics

result.text and result.utterances map directly to AssemblyAI's text and utterances. The SDK accepts options as keyword arguments or as a TranscribeOptions config object, like AssemblyAI's TranscriptionConfig.

Authentication

AssemblyAI sends the key in a bare authorization header (no Bearer prefix). Speech Revolutions uses X-API-Key, read from the environment by the SDK.

AssemblyAISpeech Revolutions
authorization: ASSEMBLYAI_API_KEYX-API-Key: SPEECHREVOLUTIONS_API_KEY
ASSEMBLYAI_API_KEY env varSPEECHREVOLUTIONS_API_KEY

Endpoint & method mapping

AssemblyAISpeech Revolutions RESTSpeech Revolutions SDK
POST /v2/upload (raw bytes → upload_url)POST /api/v1/transcribe (raw bytes in, transcript streamed back; up to 200 MB)submit() (or transcribe() to also wait)
POST /v2/transcript (audio_url → job id)
GET /v2/transcript/{id} (poll until completed)GET /api/v1/jobs/{id} or /stream (SSE)get_job_status() / get_transcript()

AssemblyAI uploads first, returns an upload_url, then creates a transcript from it. In a Speech Revolutions SDK this is one call: pass a path or URL to transcribe() and it uploads the file, waits, and returns the transcript. To test from a terminal, /api/v1/transcribe does the same in one request.

Upload differences

Both platforms accept a hosted URL. If you already pass an audio_url that points at your own storage, pass the same URL to transcribe(). For local files, AssemblyAI streams bytes to /v2/upload; Speech Revolutions streams them to a presigned object-storage URL. Speech Revolutions also reports live upload and transcribe progress that you can render as a progress bar instead of polling a status field (see live progress).

Response shape

AssemblyAI returns a flat object with text and a words[] array, with timestamps in milliseconds. Speech Revolutions returns timestamps in seconds.

AssemblyAI fieldSpeech Revolutions
textresult.text
words[] (text, start/end in ms, speaker)result.words (text, start/end in seconds, speaker)
utterances[] (speaker turns)result.utterances
language_coderesult.languages
Watch the units

AssemblyAI timestamps are in milliseconds; Speech Revolutions timestamps are in seconds. If your code divides by 1000, remove that step.

Diarization

AssemblyAI enables diarization with speaker_labels: true. Speech Revolutions uses the same speaker_labels flag (on by default) and returns utterances in the same way, so speaker-turn code ports with few changes. Zephyr ranks #1 on diarization error rate on every benchmark subset: 9.7% on AMI, against 25.3% for AssemblyAI. See the benchmarks and the comparison table.

Timestamps

Both return word timestamps. The only change is the unit (milliseconds → seconds). For timestamp precision, see the benchmarks.

Language selection

AssemblyAI takes language_code, or language_detection: true to auto-detect. Speech Revolutions auto-detects by default, so remove language_detection from your request. Detection runs per segment, so result.languages reports language spans rather than one language for the whole file. To set a fixed language, replace language_code with language and an ISO 639-1 code (for example, en). This skips detection for the whole file; see pinning the language.

Side by side

assemblyai_transcribe.py

From AssemblyAI — Python (assemblyai_transcribe.py)

import assemblyai as aai

aai.settings.api_key = "..."  # ASSEMBLYAI_API_KEY
transcriber = aai.Transcriber()

config = aai.TranscriptionConfig(speaker_labels=True)
transcript = transcriber.transcribe("meeting.mp3", config)  # uploads + polls

print(transcript.text)
for u in transcript.utterances:
    print(f"{u.speaker}: {u.text}")
for w in transcript.words:
    print(w.text, w.start, w.end)  # start/end in milliseconds

Non-blocking, if you polled before

If your AssemblyAI code submits and polls on its own schedule (for example, from a worker), use submit() and get_job_status() instead of the blocking transcribe():

python
job_id = client.submit("meeting.mp3", speaker_labels=True)

status = client.get_job_status(job_id)
if status.is_completed:
    result = client.get_transcript(job_id)
    print(result.text)

To skip polling, set a callback_url webhook. Speech Revolutions sends a signed POST when the job finishes.

Common pitfalls

  • Timestamp units. Milliseconds → seconds. This is the most common bug after migrating.
  • Auth header name. authorization → X-API-Key.
  • Upload flow. AssemblyAI uploads, then references the upload URL in a second request. The Speech Revolutions SDK does both in one transcribe() call.
  • Keyword biasing. AssemblyAI's keyterms_prompt and the deprecated word_boost both map to custom_vocabulary.

See the migration playbook for cutover, and the benchmarks for accuracy, diarization, and timestamp precision.