Transcription sends audio to a speech-to-text model and returns the text, with timestamps when you request them.
You need a React app with a server route, and an OpenAI API key. This takes about 5 minutes.
npm i @tanstack/ai @tanstack/ai-react @tanstack/ai-openaipnpm add @tanstack/ai @tanstack/ai-react @tanstack/ai-openaiyarn add @tanstack/ai @tanstack/ai-react @tanstack/ai-openaibun add @tanstack/ai @tanstack/ai-react @tanstack/ai-openaiOPENAI_API_KEY=sk-...import {
generateTranscription,
generationParamsFromRequest,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'
export async function POST(request: Request) {
const { input, threadId } = await generationParamsFromRequest('transcription', request)
if (typeof input.audio !== 'string') {
return new Response('Send the audio as a data URL.', { status: 400 })
}
const stream = generateTranscription({
adapter: openaiTranscription('gpt-4o-transcribe'),
audio: input.audio,
language: input.language,
stream: true,
threadId,
})
return toServerSentEventsResponse(stream)
}import type { ChangeEvent } from 'react'
import { fetchServerSentEvents, useTranscription } from '@tanstack/ai-react'
export function Transcriber() {
const { generate, result, isLoading, error } = useTranscription({
connection: fetchServerSentEvents('/api/transcribe'),
threadId: 'transcription',
})
function handleFile(event: ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0]
if (!file) return
const reader = new FileReader()
reader.onload = () => {
if (typeof reader.result === 'string') {
void generate({ audio: reader.result, language: 'en' })
}
}
reader.readAsDataURL(file)
}
return (
<div>
<input type="file" accept="audio/*" onChange={handleFile} />
{isLoading && <p>Transcribing...</p>}
{error && <p role="alert">Error: {error.message}</p>}
{result && <p>{result.text}</p>}
</div>
)
}Pick an audio file. The text of the recording shows up below the input.
To record from the microphone instead of a file, see Audio Recording.
The result can also hold:
The other transports (a server function that returns JSON, or one that returns a stream) are in Generations. Every useTranscription input field is in Generation Hooks, and the hook options are in Options.
To keep the status and the result of a long file after a page reload, add Generation Persistence.
You do not need a hook to transcribe a file you already have on the server. Call generateTranscription() without stream and read the result:
import { readFile } from 'node:fs/promises'
import { generateTranscription } from '@tanstack/ai'
import { openaiTranscription } from '@tanstack/ai-openai'
const bytes = await readFile('./meeting-recording.mp3')
const audio = new File([bytes], 'meeting-recording.mp3', { type: 'audio/mpeg' })
const result = await generateTranscription({
adapter: openaiTranscription('gpt-4o-transcribe'),
audio,
language: 'en',
threadId: 'thread-1',
})
console.log(result.text)audio accepts any of these:
Swap the adapter to change the provider. Each adapter reads its key from the environment.
| Provider | Adapter | Models | Key |
|---|---|---|---|
| OpenAI | openaiTranscription | gpt-4o-transcribe, gpt-4o-mini-transcribe, gpt-4o-transcribe-diarize, whisper-1 | OPENAI_API_KEY |
| ElevenLabs | elevenlabsTranscription | scribe_v2, scribe_v2_medical | ELEVENLABS_API_KEY |
| Groq | groqTranscription | whisper-large-v3-turbo, whisper-large-v3 | GROQ_API_KEY |
| xAI | grokTranscription | grok-stt | XAI_API_KEY |
| BytePlus | byteplusTranscription | seed-asr | BYTEPLUS_VOICE_API_KEY |
| fal.ai | falTranscription | fal-ai/whisper, Wizper, and other fal speech-to-text endpoints | FAL_KEY |
| Cloudflare | cloudflareTranscription | @cf/openai/whisper-large-v3-turbo, @cf/deepgram/nova-3 | CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN |
| Lovable | lovableTranscription | openai/gpt-4o-transcribe, openai/gpt-4o-mini-transcribe | LOVABLE_API_KEY |
Need speaker labels? Use gpt-4o-transcribe-diarize, or BytePlus with enable_speaker_info. Examples for each provider are in Transcription: Advanced.
| Option | Type | Description |
|---|---|---|
| audio | string | File | Blob | ArrayBuffer | The audio to transcribe (required) |
| language | string | The language in ISO-639-1 format, for example "en", "es", or "ja". A correct language makes the result more accurate and faster. |
| prompt | string | Words or names the model must expect, for example "Technical terms: API, SDK, CLI". gpt-4o-transcribe-diarize does not support it. |
| responseFormat | 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt' | The output format. verbose_json adds timestamps and segments. |
| modelOptions | object | Provider options. They are typed for the model you pass. |
| debug | DebugOption | Log the request and every raw provider chunk. See Debug Logging. |
Provider examples, OpenAI model options, speaker diarization, the result type, audio formats, errors, and explicit API keys are in Transcription: Advanced.