← Code Recipes
Speech & Audio

Deepgram Free code examples in C#, Python, Java and JavaScript

Fast, accurate transcription with real-time streaming and diarisation.

C# Python Java JavaScript

WHAT THIS PAGE COVERS

A live WebSocket transcript with speaker labels.

Model nova-3
Package deepgram-sdk
Checked

Model ids change. If a sample returns a 404 on the model, that is the first thing to check against the official docs.

Install

The NuGet package is `Deepgram`. Library.Initialize() sets up the native logging and must run before the first client is created.

dotnet add package Deepgram

setx DEEPGRAM_API_KEY "your-key"       # Windows, then open a new shell
export DEEPGRAM_API_KEY="your-key"     # macOS / Linux
Transcribe a file

ClientFactory reads DEEPGRAM_API_KEY from the environment. The file goes as a byte array, so a very long recording is worth streaming through the live client instead.

using Deepgram;
using Deepgram.Models.Listen.v1.REST;

Library.Initialize();

var deepgram = ClientFactory.CreateListenRESTClient();

var audio = await File.ReadAllBytesAsync("meeting.wav");

var response = await deepgram.TranscribeFile(
    audio,
    new PreRecordedSchema
    {
        Model = "nova-3",
        SmartFormat = true,
    });

Console.WriteLine(response.Results.Channels[0].Alternatives[0].Transcript);
Speaker labels on a recording

Diarize gives every word a speaker index. Grouping consecutive words by that index is what turns the flat transcript into something that reads like a meeting.

var diarised = await deepgram.TranscribeFile(
    audio,
    new PreRecordedSchema
    {
        Model = "nova-3",
        SmartFormat = true,
        Diarize = true,
    });

var words = diarised.Results.Channels[0].Alternatives[0].Words;

int? current = null;
foreach (var word in words)
{
    if (word.Speaker != current)
    {
        current = word.Speaker;
        Console.Write($"{Environment.NewLine}[speaker {current}] ");
    }

    Console.Write($"{word.PunctuatedWord ?? word.Word} ");
}
Failing usefully

A 401 is a bad key and a 400 is usually an audio format the decoder could not read. Both come back as exceptions from the SDK, and both are worth distinguishing before a retry loop turns a permanent failure into a slow one.

try
{
    var result = await deepgram.TranscribeFile(
        audio,
        new PreRecordedSchema { Model = "nova-3" });

    Console.WriteLine(result.Results.Channels[0].Alternatives[0].Transcript);
}
catch (HttpRequestException error)
{
    // Retrying a 401 or a 400 will never succeed — only 429 and 5xx
    // are worth a second attempt.
    Console.Error.WriteLine($"Deepgram rejected the request: {error.StatusCode}");
    throw;
}
Install

The client reads DEEPGRAM_API_KEY from the environment when you construct it with no arguments.

pip install deepgram-sdk

export DEEPGRAM_API_KEY="your-key"     # macOS / Linux
setx DEEPGRAM_API_KEY "your-key"       # Windows, then open a new shell
Transcribe a file

smart_format is worth switching on before anything else: it punctuates, and it formats currency, phone numbers and email addresses. Without it you get a lowercase run-on that is technically accurate and hard to read.

import os
from deepgram import DeepgramClient

deepgram = DeepgramClient(api_key=os.environ["DEEPGRAM_API_KEY"])

with open("meeting.wav", "rb") as audio_file:
    response = deepgram.listen.v1.media.transcribe_file(
        request=audio_file.read(),
        model="nova-3",
        smart_format=True,
    )

transcript = response.results.channels[0].alternatives[0].transcript
print(transcript)
Live transcription with speaker labels

diarize is what turns a transcript into a conversation. Each word carries a speaker index, and the indices are stable for the life of the connection — they are NOT stable across connections, so do not persist them as identities.

from deepgram.extensions.types.sockets import ListenV1SocketClientResponse
from deepgram.core.events import EventType

def on_message(message: ListenV1SocketClientResponse) -> None:
    alternative = message.channel.alternatives[0]
    if not alternative.transcript:
        return

    # Interim results arrive repeatedly and get replaced; only the
    # final ones are worth appending to a saved transcript.
    if message.is_final:
        speaker = alternative.words[0].speaker if alternative.words else "?"
        print(f"[speaker {speaker}] {alternative.transcript}")

with deepgram.listen.v1.connect(
    model="nova-3",
    diarize=True,
    interim_results=True,
    encoding="linear16",
    sample_rate=16000,
) as connection:
    connection.on(EventType.MESSAGE, on_message)
    connection.start_listening()
Feeding it audio

Send raw PCM frames, not a WAV file — the header confuses the decoder when encoding and sample_rate are already declared. Roughly 20ms per send keeps latency low without flooding the socket.

import time

CHUNK_BYTES = 640     # 20ms of 16kHz mono 16-bit PCM

with open("meeting.raw", "rb") as source:
    while True:
        frame = source.read(CHUNK_BYTES)
        if not frame:
            break

        connection.send_media(frame)

        # A file reads far faster than real time; without this the
        # whole recording is sent in a second and the "live"
        # behaviour you are testing never happens.
        time.sleep(0.02)
Install

DEEPGRAM'S JAVA SDK IS PRE-1.0 (0.10.x) AND ITS REQUEST-OPTIONS SURFACE IS NOT PUBLISHED, so this uses the documented HTTP API instead. java.net.http has been in the JDK since 11, the model is a query parameter, and the shape will outlive the 0.x SDK.

# No dependency. java.net.http is part of the JDK (11+).
export DEEPGRAM_API_KEY="your-key"
Transcribe a hosted file

The Authorization header is `Token <key>` — not `Bearer`, which is the reflex and returns 401. Options ride on the query string, which is why the model is in the URL rather than the body.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Transcribe {
    public static void main(String[] args) throws Exception {
        String apiKey = System.getenv("DEEPGRAM_API_KEY");
        if (apiKey == null) throw new IllegalStateException("DEEPGRAM_API_KEY is not set.");

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(
                "https://api.deepgram.com/v1/listen?model=nova-3&smart_format=true"))
            .header("Authorization", "Token " + apiKey)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(
                "{\"url\":\"https://dpgr.am/spacewalk.wav\"}"))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.body());
    }
}
Uploading local audio instead

Swap the JSON body for the audio bytes and set Content-Type to the media type. Nothing else changes — same URL, same header, same response shape.

import java.nio.file.Path;

HttpRequest upload = HttpRequest.newBuilder()
    .uri(URI.create(
        "https://api.deepgram.com/v1/listen?model=nova-3&smart_format=true&diarize=true"))
    .header("Authorization", "Token " + apiKey)
    .header("Content-Type", "audio/wav")
    .POST(HttpRequest.BodyPublishers.ofFile(Path.of("meeting.wav")))
    .build();

HttpResponse<String> response = HttpClient.newHttpClient()
    .send(upload, HttpResponse.BodyHandlers.ofString());
Reading the transcript out

results.channels[0].alternatives[0].transcript. The two arrays are multi-channel audio and alternative hypotheses; with mono audio and default settings both have exactly one entry.

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(response.body());

JsonNode alternative = root
    .path("results")
    .path("channels").path(0)
    .path("alternatives").path(0);

System.out.println(alternative.path("transcript").asText());

// With diarize=true every word carries a speaker index.
for (JsonNode word : alternative.path("words")) {
    System.out.printf("[%d] %s%n",
        word.path("speaker").asInt(), word.path("word").asText());
}
Install

Server-side. The browser cannot hold this key, so live transcription from a web page means proxying the socket through your own server.

npm install @deepgram/sdk

export DEEPGRAM_API_KEY="your-key"
Transcribe a file

The transcript is four levels deep — results.channels[0].alternatives[0].transcript. Two of those arrays are for multi-channel audio and alternative hypotheses; with one speaker and default settings both are length one.

import fs from "node:fs";
import { createClient } from "@deepgram/sdk";

const deepgram = createClient(process.env.DEEPGRAM_API_KEY);

const result = await deepgram.listen.v1.media.transcribeFile(
  fs.createReadStream("meeting.wav"),
  {
    model: "nova-3",
    smart_format: true,
  },
);

console.log(result.results.channels[0].alternatives[0].transcript);
A live socket with speaker labels

waitForOpen matters: sending audio before the socket is open drops it silently, and the symptom is an empty transcript rather than an error.

const connection = deepgram.listen.v1.connect({
  model: "nova-3",
  diarize: true,
  interim_results: true,
  encoding: "linear16",
  sample_rate: 16000,
});

connection.on("open", () => console.log("socket open"));
connection.on("error", (error) => console.error(error));
connection.on("close", () => console.log("socket closed"));

connection.on("message", (data) => {
  const alternative = data.channel?.alternatives?.[0];
  if (!alternative?.transcript) return;

  if (data.is_final) {
    const speaker = alternative.words?.[0]?.speaker ?? "?";
    console.log(`[speaker ${speaker}] ${alternative.transcript}`);
  }
});

connection.connect();
await connection.waitForOpen();
Piping microphone or file audio in

sendMedia takes raw PCM frames. A WAV header sent as the first frame is read as audio and produces a click, then nonsense — strip it or capture raw.

import { setTimeout as sleep } from "node:timers/promises";

const CHUNK_BYTES = 640;      // 20ms of 16kHz mono 16-bit PCM
const source = fs.createReadStream("meeting.raw", {
  highWaterMark: CHUNK_BYTES,
});

for await (const frame of source) {
  connection.sendMedia(frame);

  // Pace it to real time, or the whole file is sent at once and the
  // interim-results behaviour never shows up.
  await sleep(20);
}

connection.close();

QUESTIONS

Which languages are the Deepgram examples in?

C#, Python, Java and JavaScript. Every sample runs as pasted and reads its credentials from an environment variable — no key is ever written into the code.

Which package do I install for Deepgram?

The samples use deepgram-sdk. The install step on each language tab gives the exact command.

Which Deepgram model do these examples use?

nova-3, checked against the official documentation on 26 September 2026. Model ids change often; if a call returns a model-not-found error, check the current list in the official docs.

Where is the official Deepgram documentation?

https://developers.deepgram.com/ — it is linked at the bottom of this page, and it is the authority whenever it disagrees with a sample here.

ALSO IN SPEECH & AUDIO

The authority

Official Deepgram documentation

Model names, pricing and rate limits change faster than any page here can. When this page and the official docs disagree, the official docs are right.

Read the docs ↗

https://developers.deepgram.com/

New drops, every week

Free tools & Claude templates, landing regularly.

We keep shipping new free tools, code recipes and Claude Code templates. Follow @freeyantra on Instagram or join the newsletter so you never miss one.

Follow @freeyantra

or get them by email

Double opt-in. Unsubscribe any time. Privacy