Transcription

Turn audio into text with a shared TranscriptionModel API.

Every transcription provider conforms to TranscriptionModel and works with transcribe.

let result = try await transcribe(
  model: OpenAITranscriptionModel("whisper-1"),
  audio: audioData,
  mediaType: "audio/mpeg"
)

print(result.text)
for segment in result.segments {
  print("\(segment.startSecond)s–\(segment.endSecond)s", segment.text)
}

Results include the text plus segments, detected language, and duration when the provider returns them. When mediaType is generic (application/octet-stream, audio/*, or empty), the container is sniffed from the bytes — MP4/M4A from its ftyp box, plus WAV, Ogg, FLAC, and MP3 — so a mislabeled upload still reaches the provider correctly.

Streaming transcription

streamTranscribe transcribes live audio, emitting updates before the audio is complete. Models opt in by conforming to StreamingTranscriptionModel; DeepgramTranscriptionModel does, over Deepgram's live WebSocket API.

let result = try streamTranscribe(
  model: DeepgramTranscriptionModel("nova-3"),
  audio: microphoneChunks,          // AsyncThrowingStream<Data, Error>
  mediaType: "audio/pcm"
)

for try await part in result.fullStream {
  switch part {
  case .partialTranscript(let text): draft = text          // replaces
  case .transcriptDelta(let text): transcript += text      // appends
  case .speechStart, .speechEnd, .segment, .language: break
  case .finish(let response): print(response.text)
  }
}

Interim results arrive as .partialTranscript (each one replaces the last) and finalized text as .transcriptDelta (each one appends), so textStream and the text promise never double-count a revised phrase.

fullStream is single-consumer with no replay: read it once, or skip it and await result.text / .segments / .language, which drain the stream internally and cache. Cancelling the result — or a failure before streaming starts, such as a bad key — cancels the audio stream you passed in, so an upstream producer never hangs.

Native request fields go in providerOptions:

let result = try await transcribe(
  model: OpenAITranscriptionModel("whisper-1"),
  audio: audioData,
  mediaType: "audio/wav",
  providerOptions: ["openai": ["language": "en", "temperature": 0]]
)

AssemblyAI, Rev.ai, and Gladia are asynchronous upstream. Their model types submit, poll, and fetch internally, so your call remains a single await.

Models

ProviderModel typeDefault or example modelKey
OpenAIOpenAITranscriptionModelwhisper-1OPENAI_API_KEY
ElevenLabsElevenLabsTranscriptionModelscribe_v2ELEVENLABS_API_KEY
DeepgramDeepgramTranscriptionModelnova-3DEEPGRAM_API_KEY
AssemblyAIAssemblyAITranscriptionModeluniversal-3-5-proASSEMBLYAI_API_KEY
Rev.aiRevAITranscriptionModelmachineREVAI_API_KEY
GladiaGladiaTranscriptionModelsolaria-1GLADIA_API_KEY
SarvamSarvamTranscriptionModelsaaras:v3SARVAM_API_KEY

Groq-hosted Whisper also works through OpenAITranscriptionModel with Groq's base URL; see the Groq provider page.