SubQ
Integrations

Migrate from Deepgram

Switch from Deepgram to SubQ with a 2-line code change

SubQ implements Deepgram-compatible APIs. If you already use a Deepgram SDK, you can switch to SubQ by changing the base URL and API key. The SDKs, request format, and response structure stay the same.

What changes

DeepgramSubQ
REST base URLhttps://api.deepgram.comhttps://stt-api.subq.ai
WebSocket URLwss://api.deepgram.comwss://stt-api.subq.ai
API key prefixdg_org_
Auth header (REST)Authorization: Token dg_...Authorization: Bearer org_...
Auth header (WebSocket)Sec-WebSocket-Protocol: token, dg_...Sec-WebSocket-Protocol: token, org_...

The API endpoints (/v1/listen), query parameters, and JSON response format are identical. Your existing parsing logic does not need to change.

Step 1: Get a SubQ API key

Sign up at speech.subq.ai and generate an API key from the API Keys page. Your key starts with org_.

Step 2: Update the SDK configuration

The only code change is pointing the SDK to stt-api.subq.ai instead of api.deepgram.com.

The Python Deepgram SDK uses a DeepgramClientEnvironment object to configure where API requests are sent. By default, it points at api.deepgram.com. To migrate, you create a custom environment that redirects all REST and WebSocket traffic to stt-api.subq.ai instead.

Replace your existing client setup:

Before (Deepgram):

from deepgram import DeepgramClient

# The default client connects to api.deepgram.com
client = DeepgramClient("dg_YOUR_DEEPGRAM_KEY")

After (SubQ):

from deepgram import DeepgramClient, DeepgramClientEnvironment

# Redirect all SDK traffic to the SubQ API
config = DeepgramClientEnvironment(
    base="https://stt-api.subq.ai",        # REST endpoint for pre-recorded transcription
    production="wss://stt-api.subq.ai",  # WebSocket endpoint for streaming
    agent="wss://stt-api.subq.ai"        # Agent WebSocket (set to same as production)
)

client = DeepgramClient(api_key="YOUR_SUBQ_API_KEY", environment=config)

The three fields in DeepgramClientEnvironment control where each type of request is routed. base handles REST calls like transcribe_file() and transcribe_url(). production handles WebSocket connections for streaming. agent is used for agent-based connections and should be set to the same value as production.

After this change, the rest of your code stays exactly the same. Any calls to client.listen.v1.media.transcribe_file(), transcribe_url(), or client.listen.v1.connect() work without modification. Only the destination changes.

Requirements: Python 3.8+, deepgram-sdk>=3.4.0

The Node.js SDK accepts a global.url option that overrides the base URL for all requests. Setting this to https://stt-api.subq.ai redirects both REST and WebSocket traffic.

Before (Deepgram):

import { createClient } from "@deepgram/sdk";

// The default client connects to api.deepgram.com
const client = createClient({ key: "dg_YOUR_DEEPGRAM_KEY" });

After (SubQ):

import { createClient } from "@deepgram/sdk";

// Redirect all SDK traffic to the SubQ API
const client = createClient({
  key: "YOUR_SUBQ_API_KEY",
  global: { url: "https://stt-api.subq.ai" }
});

The global.url field sets the base URL for every SDK request. The SDK automatically derives the WebSocket URL from this, so both pre-recorded and streaming calls are redirected.

For pre-recorded transcription, the Node.js SDK may require accessToken instead of key depending on your SDK version:

const client = createClient({
  accessToken: "YOUR_SUBQ_API_KEY",
  global: { url: "https://stt-api.subq.ai" }
});

All client.listen.prerecorded and client.listen.live calls work as before. Only the destination changes.

The Go SDK accepts Host and AccessToken fields in the ClientOptions struct. Setting Host to https://stt-api.subq.ai redirects all API calls. Pass your SubQ key via AccessToken instead of the first positional argument.

Before (Deepgram):

// The default client connects to api.deepgram.com
client := dglisten.NewREST("dg_YOUR_DEEPGRAM_KEY", nil)

After (SubQ):

// Redirect all SDK traffic to the SubQ API
client := dglisten.NewREST("", &dgi.ClientOptions{
    Host:        "https://stt-api.subq.ai", // REST and WebSocket base URL
    AccessToken: "YOUR_SUBQ_API_KEY",            // SubQ API key (starts with org_)
})

The first argument is left empty because the API key is provided through AccessToken in the options struct instead. The DoFile(), DoURL(), and WebSocket methods work identically. Only the destination changes.

Step 3: Verify it works

Run a quick test to confirm the migration is working. These examples use the same SDK methods you already have. The only difference is the client configuration from Step 2. If the test produces a transcript, your migration is complete.

Pre-recorded transcription

Send a local audio file through the SDK to verify that REST transcription works with the SubQ backend.

This reads audio.wav from disk, sends it to SubQ via the SDK's transcribe_file() method, and prints the top transcript result:

with open("audio.wav", "rb") as f:
    response = client.listen.v1.media.transcribe_file(request=f.read())
    print(response.results.channels[0].alternatives[0].transcript)

This creates a read stream from audio.wav, sends it to SubQ for transcription, and logs the result. The response?.result ?? response pattern handles different SDK response wrapper versions:

import fs from "fs";

const response = await client.listen.prerecorded.transcribeFile(
  fs.createReadStream("audio.wav"),
  { mimetype: "audio/wav" }
);

const result = response?.result ?? response;
console.log(result.results.channels[0].alternatives[0].transcript);

This sends audio.wav to SubQ using the SDK's DoFile() method. The response is unmarshalled into a Response struct, and the transcript is extracted from the first channel's top alternative:

var resp Response
if err := client.DoFile(ctx, "audio.wav",
    &dgi.PreRecordedTranscriptionOptions{}, &resp); err != nil {
    panic(err)
}
fmt.Println(resp.Results.Channels[0].Alternatives[0].Transcript)

Real-time streaming

Streaming verification confirms that WebSocket connections work with the SubQ backend. This example connects to a live audio stream and prints transcripts as they arrive.

This opens a WebSocket connection to wss://stt-api.subq.ai/v1/listen through the SDK, listens for transcript events, and streams audio from a live internet radio source. The LiveTranscriptionEvents.Transcript event fires each time the server sends a transcript message, and the LiveTranscriptionEvents.Open event fires once the connection is established:

import { createClient, LiveTranscriptionEvents } from "@deepgram/sdk";

const client = createClient({
  key: "YOUR_SUBQ_API_KEY",
  global: { url: "https://stt-api.subq.ai" }
});

// Open a live transcription connection with MP3 encoding
const connection = client.listen.live({
  encoding: "mp3",
  interim_results: true
});

// Print transcripts as they arrive
connection.on(LiveTranscriptionEvents.Transcript, (data) => {
  const transcript = data?.channel?.alternatives?.[0]?.transcript;
  if (transcript) console.log(transcript);
});

// Once the connection is open, start streaming audio
connection.on(LiveTranscriptionEvents.Open, async () => {
  const stream = await fetch("http://icecast.omroep.nl/radio1-bb-mp3");
  const reader = stream.body.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    connection.send(value);
  }
});

The Python and Go Deepgram SDKs may produce authentication errors during WebSocket streaming due to SDK-specific auth handling. Pre-recorded transcription works correctly with all SDKs. For streaming, use the Node.js SDK or connect via raw WebSocket.

Without the Deepgram SDK

If you prefer not to use a Deepgram SDK, you can call the SubQ API directly using standard HTTP clients. The endpoint (POST /v1/listen) and JSON response format are identical whether you use an SDK or not. This approach gives you full control over the request and removes the Deepgram SDK dependency entirely.

Send audio bytes directly to the SubQ REST endpoint using httpx. The Bearer token authenticates the request, and the transcript is extracted from the JSON response:

import httpx

response = httpx.post(
    "https://stt-api.subq.ai/v1/listen",
    headers={"Authorization": "Bearer YOUR_SUBQ_API_KEY"},
    content=open("audio.wav", "rb").read(),
    timeout=60.0
)

result = response.json()
print(result["results"]["channels"][0]["alternatives"][0]["transcript"])

Use the built-in fetch API to POST audio bytes to SubQ. The response JSON follows the same structure as the SDK response:

import { readFileSync } from "fs";

const response = await fetch("https://stt-api.subq.ai/v1/listen", {
  method: "POST",
  headers: { "Authorization": "Bearer YOUR_SUBQ_API_KEY" },
  body: readFileSync("audio.wav")
});

const result = await response.json();
console.log(result.results.channels[0].alternatives[0].transcript);

Read the audio file, create an HTTP request with a Bearer auth header, and parse the JSON response. The type assertions navigate the nested response structure to extract the transcript:

audioData, _ := os.ReadFile("audio.wav")

req, _ := http.NewRequest("POST",
    "https://stt-api.subq.ai/v1/listen",
    bytes.NewReader(audioData))
req.Header.Set("Authorization", "Bearer YOUR_SUBQ_API_KEY")

resp, err := http.DefaultClient.Do(req)
if err != nil {
    panic(err)
}
defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)
var result map[string]interface{}
json.Unmarshal(body, &result)

transcript := result["results"].(map[string]interface{})["channels"].([]interface{})[0].(map[string]interface{})["alternatives"].([]interface{})[0].(map[string]interface{})["transcript"].(string)
fmt.Println(transcript)

Troubleshooting

If you run into issues after migrating, the most common causes are URL typos, incorrect API keys, or SDK-specific WebSocket authentication behavior. The following table covers the errors you're most likely to encounter and how to resolve them.

ErrorFix
Connection refusedVerify the base URL is https://stt-api.subq.ai
401 UnauthorizedCheck that your API key starts with org_ and is passed correctly
503 Service UnavailableServer is busy. Retry after the Retry-After header value
415 Unsupported MediaVerify the audio format is supported (MP3, WAV, AAC, FLAC, OGG, WebM, Opus, M4A)
ModuleNotFoundError (Python)Run pip install "deepgram-sdk>=3.4.0"
unknown deepgram error (streaming)Python/Go SDK WebSocket auth issue. Use Node.js SDK or raw WebSocket for streaming

Next steps