Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Implement production pre-recorded speech-to-text with Deepgram. Use when building audio transcription, batch processing, or implementing diarization and intelligence features. Trigger: "deepgram transcription", "speech to text", "transcribe audio", "batch transcription", "deepgram nova", "diarize audio".
.claude/skills/jeremylongshore-deepgram-core-workflow-a/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-14 | ✗→✓ | ▲ Improved | 52% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 54% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 31% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 74% | 0% |
| case-19 | ✗→✓ | ▲ Improved | 30% | 0% |
Production pre-recorded transcription service using Deepgram's REST API. Covers transcribeUrl and transcribeFile, speaker diarization, audio intelligence (summarization, topic detection, sentiment, intent), batch processing with concurrency control, and callback-based async transcription for large files.
@deepgram/sdk installed, DEEPGRAM_API_KEY configuredp-limit package (npm install p-limit)typescriptimport { createClient, DeepgramClient } from '@deepgram/sdk'; import { readFileSync } from 'fs'; interface TranscribeOptions { model?: 'nova-3' | 'nova-2' | 'nova-2-meeting' | 'nova-2-phonecall' | 'base'; language?: string; diarize?: boolean; utterances?: boolean; paragraphs?: boolean; smart_format?: boolean; summarize?: boolean; // Audio intelligence detect_topics?: boolean; // Topic detection sentiment?: boolean; // Sentiment analysis intents?: boolean; // Intent recognition keywords?: string[]; // Keyword boosting: ["term:weight"] callback?: string; // Async callback URL } class DeepgramTranscriber { private client: DeepgramClient; constructor(apiKey: string) { this.client = createClient(apiKey); } async transcribeUrl(url: string, opts: TranscribeOptions = {}) { const { result, error } = await this.client.listen.prerecorded.transcribeUrl( { url }, { model: opts.model ?? 'nova-3', language: opts.language ?? 'en', smart_format: opts.smart_format ?? true, diarize: opts.diarize ?? false, utterances: opts.utterances ?? false, paragraphs: opts.paragraphs ?? false, summarize: opts.summarize ? 'v2' : undefined, detect_topics: opts.detect_topics ?? false, sentiment: opts.sentiment ?? false, intents: opts.intents ?? false, keywords: opts.keywords, callback: opts.callback, } ); if (error) throw new Error(`Transcription failed: ${error.message}`); return result; } async transcribeFile(filePath: string, opts: TranscribeOptions = {}) { const audio = readFileSync(filePath); const mimetype = this.detectMimetype(filePath); const { result, error } = await this.client.listen.prerecorded.transcribeFile( audio, { model: opts.model ?? 'nova-3', smart_format: opts.smart_format ?? true, mimetype, diarize: opts.diarize ?? false, utterances: opts.utterances ?? false, summarize: opts.summarize ? 'v2' : undefined, detect_topics: opts.detect_topics ?? false, sentiment: opts.sentiment ?? false, } ); if (error) throw new Error(`File transcription failed: ${error.message}`); return result; } private detectMimetype(path: string): string { const ext = path.split('.').pop()?.toLowerCase(); const map: Record<string, string> = { wav: 'audio/wav', mp3: 'audio/mpeg', flac: 'audio/flac', ogg: 'audio/ogg', m4a: 'audio/mp4', webm: 'audio/webm', }; return map[ext ?? ''] ?? 'audio/wav'; } }
typescriptfunction formatResult(result: any) { const channel = result.results.channels[0]; const alt = channel.alternatives[0]; return { transcript: alt.transcript, confidence: alt.confidence, words: alt.words?.map((w: any) => ({ word: w.word, start: w.start, end: w.end, confidence: w.confidence, speaker: w.speaker, // Only if diarize: true punctuated_word: w.punctuated_word, })), // Speaker segments (requires utterances: true + diarize: true) utterances: result.results.utterances?.map((u: any) => ({ speaker: u.speaker, text: u.transcript, start: u.start, end: u.end, confidence: u.confidence, })), // Audio intelligence results summary: result.results.summary?.short, topics: result.results.topics?.segments, sentiments: result.results.sentiments?.segments, intents: result.results.intents?.segments, metadata: { duration: result.metadata.duration, channels: result.metadata.channels, model: result.metadata.model_info, request_id: result.metadata.request_id, }, }; }
typescriptimport pLimit from 'p-limit'; async function batchTranscribe( files: string[], opts: TranscribeOptions = {}, concurrency = 5 ) { const transcriber = new DeepgramTranscriber(process.env.DEEPGRAM_API_KEY!); const limit = pLimit(concurrency); const results = await Promise.allSettled( files.map(file => limit(async () => { const result = await transcriber.transcribeFile(file, opts); console.log(`Done: ${file} (${result.metadata.duration}s)`); return { file, result: formatResult(result) }; }) ) ); const succeeded = results.filter(r => r.status === 'fulfilled'); const failed = results.filter(r => r.status === 'rejected'); console.log(`Batch complete: ${succeeded.length} ok, ${failed.length} failed`); return results; }
typescript// For files >2 hours or when you don't want to hold a connection open, // use Deepgram's callback feature. Deepgram POSTs results to your URL. async function submitAsync(audioUrl: string, callbackUrl: string) { const transcriber = new DeepgramTranscriber(process.env.DEEPGRAM_API_KEY!); // Deepgram returns a request_id immediately, processes in background const result = await transcriber.transcribeUrl(audioUrl, { model: 'nova-3', diarize: true, callback: callbackUrl, // Your HTTPS endpoint }); console.log('Submitted. Request ID:', result.metadata.request_id); // Deepgram will POST results to callbackUrl when done // Retries up to 10 times with 30s delay on failure }
typescript// Boost domain-specific terms for higher accuracy const result = await transcriber.transcribeUrl(audioUrl, { model: 'nova-3', keywords: [ 'Kubernetes:1.5', // Boost weight 1.0-2.0 'PostgreSQL:1.5', 'microservices:1.3', ], });
DeepgramTranscriber class with URL and file transcriptionp-limit| Error | Cause | Solution | |-------|-------|----------| | 400 Bad Request | Invalid audio format | Verify file header bytes (WAV: RIFF, MP3: 0xFFF3/0xFFFB) | | 413 Payload Too Large | File exceeds limit | Use callback URL for async processing | | Empty transcript | No speech in audio | Check audio volume, try alternatives: 3 for confidence | | 408 Timeout | Long file, sync mode | Switch to callback-based async | | Low confidence | Background noise | Preprocess: ffmpeg -i input.wav -af "highpass=f=200,lowpass=f=3000" clean.wav |
Proceed to deepgram-core-workflow-b for real-time streaming transcription.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-13 | pass→pass | 9,247 | 7,010 | -24% | 1 | 1 | 0% | 1,706 | 3,577 | +110% | 0 | 0 | — |
case-14 | fail→pass | 10,446 | 3,895 | -63% | 1 | 1 | 0% | 1,905 | 2,892 | +52% | 0 | 0 | — |
case-02 | fail→pass | 16,286 | 12,121 | -26% | 1 | 1 | 0% | 3,013 | 4,631 | +54% | 0 | 0 | — |
case-03 | fail→fail | 16,679 | 13,360 | -20% | 1 | 1 | 0% | 3,413 | 5,021 | +47% | 0 | 0 | — |
case-04 | pass→pass | 10,679 | 8,349 | -22% | 1 | 1 | 0% | 1,896 | 3,730 | +97% | 0 | 0 | — |
case-01 | fail→pass | 15,791 | 11,743 | -26% | 1 | 1 | 0% | 3,854 | 5,034 | +31% | 0 | 0 | — |
case-05 | pass→pass | 7,112 | 8,044 | +13% | 1 | 1 | 0% | 1,391 | 3,698 | +166% | 0 | 0 | — |
case-06 | fail→pass | 10,122 | 7,016 | -31% | 1 | 1 | 0% | 2,066 | 3,595 | +74% | 0 | 0 | — |
case-07 | pass→pass | 12,327 | 9,314 | -24% | 1 | 1 | 0% | 2,766 | 4,233 | +53% | 0 | 0 | — |
case-08 | pass→pass | 13,550 | 10,521 | -22% | 1 | 1 | 0% | 2,758 | 4,546 | +65% | 0 | 0 | — |
case-09 | pass→pass | 10,380 | 8,406 | -19% | 1 | 1 | 0% | 2,313 | 3,985 | +72% | 0 | 0 | — |
case-10 | pass→pass | 11,581 | 8,892 | -23% | 1 | 1 | 0% | 2,636 | 4,141 | +57% | 0 | 0 | — |
case-11 | pass→pass | 14,637 | 11,945 | -18% | 1 | 1 | 0% | 2,839 | 4,691 | +65% | 0 | 0 | — |
case-12 | fail→fail | 11,203 | 11,085 | -1% | 1 | 1 | 0% | 2,034 | 4,330 | +113% | 0 | 0 | — |
case-15 | pass→pass | 9,141 | 7,267 | -21% | 1 | 1 | 0% | 1,859 | 3,656 | +97% | 0 | 0 | — |
case-16 | fail→fail | 4,087 | 4,474 | +9% | 1 | 1 | 0% | 689 | 3,037 | +341% | 0 | 0 | — |
case-17 | pass→pass | 6,627 | 2,941 | -56% | 1 | 1 | 0% | 1,156 | 2,778 | +140% | 0 | 0 | — |
case-18 | pass→pass | 5,736 | 5,219 | -9% | 1 | 1 | 0% | 1,239 | 3,179 | +157% | 0 | 0 | — |
case-19 | fail→pass | 15,332 | 9,234 | -40% | 1 | 1 | 0% | 3,261 | 4,229 | +30% | 0 | 0 | — |
case-20 | pass→pass | 10,998 | 12,060 | +10% | 1 | 1 | 0% | 2,259 | 4,697 | +108% | 0 | 0 | — |
case-21 | pass→pass | 16,362 | 14,902 | -9% | 1 | 1 | 0% | 2,842 | 5,056 | +78% | 0 | 0 | — |
case-22 | fail→fail | 13,033 | 14,300 | +10% | 1 | 1 | 0% | 2,432 | 5,093 | +109% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.