Deepgram Free code examples in C#, Python, Java and JavaScript
Fast, accurate transcription with real-time streaming and diarisation.
WHAT THIS PAGE COVERS
A live WebSocket transcript with speaker labels.
nova-3
deepgram-sdk
Model ids change. If a sample returns a 404 on the model, that is the first thing to check against the official docs.
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
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);
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} ");
}
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;
}
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
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)
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()
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)
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"
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());
}
}
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());
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());
}
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"
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);
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();
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.
https://developers.deepgram.com/