Deepgram Security Basics
Overview
Security best practices for Deepgram integration: scoped API keys, key rotation, Deepgram's built-in PII redaction feature, client-side temporary keys, SSRF prevention for audio URLs, and audit logging.
Security Checklist
Instructions
Step 1: Scoped API Keys
Create keys with minimal permissions in Console > Settings > API Keys:
const sttKey = process.env.DEEPGRAM_STT_KEY;
const ttsKey = process.env.DEEPGRAM_TTS_KEY;
const monitorKey = process.env.DEEPGRAM_MONITOR_KEY;
const adminKey = process.env.DEEPGRAM_ADMIN_KEY;
Step 2: Deepgram Built-in PII Redaction
import { createClient } from '@deepgram/sdk';
const deepgram = createClient(process.env.DEEPGRAM_API_KEY!);
const { result } = await deepgram.listen.prerecorded.transcribeUrl(
{ url: audioUrl },
{
model: 'nova-3',
smart_format: true,
redact: ['pci', 'ssn', 'numbers'],
}
);
console.log(result.results.channels[0].alternatives[0].transcript);
Step 3: Temporary Keys for Client-Side
import { createClient } from '@deepgram/sdk';
import express from 'express';
const app = express();
const deepgram = createClient(process.env.DEEPGRAM_API_KEY!);
app.post('/api/deepgram/token', async (req, res) => {
const { result, error } = await deepgram.manage.createProjectKey(
process.env.DEEPGRAM_PROJECT_ID!,
{
comment: `temp-key-${Date.now()}`,
scopes: ['listen'],
time_to_live_in_seconds: 10,
}
);
if (error) return res.status(500).json({ error: error.message });
res.json({ key: result.key, expires_in: 10 });
});
Step 4: Key Rotation
import { createClient } from '@deepgram/sdk';
async function rotateApiKey(projectId: string) {
const admin = createClient(process.env.DEEPGRAM_ADMIN_KEY!);
const { result: newKey } = await admin.manage.createProjectKey(projectId, {
comment: `rotated-${new Date().toISOString().split('T')[0]}`,
scopes: ['listen', 'speak'],
expiration_date: new Date(Date.now() + 90 * 86400000).toISOString(),
});
console.log('New key created:', newKey.key_id);
const testClient = createClient(newKey.key);
const { error } = await testClient..();
(error) ();
newKey;
}
Step 5: Audio URL Validation (SSRF Prevention)
import { URL } from 'url';
import { lookup } from 'dns/promises';
async function validateAudioUrl(url: string): Promise<void> {
const parsed = new URL(url);
if (parsed.protocol !== 'https:') {
throw new Error('Only HTTPS audio URLs allowed');
}
const { address } = await lookup(parsed.hostname);
const privateRanges = [
/^127\./, /^10\./, /^172\.(1[6-9]|2\d|3[01])\./, /^192\.168\./,
/^0\./, /^169\.254\./, /^::1$/, /^fc00:/, /^fe80:/,
];
if (privateRanges.some(r => r.test(address))) {
throw new Error(`Blocked: ${parsed.hostname} resolves to private IP`);
}
blockedHosts = [, , ];
(blockedHosts.(parsed.)) {
();
}
}
(userProvidedUrl);
{ result } = deepgram...(
{ : userProvidedUrl }, { : }
);
Step 6: Audit Logging
interface AuditEntry {
timestamp: string;
action: 'transcribe' | 'tts' | 'key_create' | 'key_delete';
userId: string;
requestId?: string;
model: string;
audioDuration?: number;
success: boolean;
error?: string;
ip?: string;
}
function logAudit(entry: AuditEntry) {
const log = {
...entry,
service: 'deepgram-integration',
level: entry.success ? 'info' : 'error',
};
console.log(JSON.stringify(log));
}
async function transcribeWithAudit(userId: string, url: string, ip: string) {
const start = Date.();
{
{ result, error } = deepgram...(
{ url }, { : , : }
);
({
: ().(),
: ,
userId, : , ip,
: result?.?.,
: result?.?.,
: !error,
: error?.,
});
(error) error;
result;
} (: ) {
({
: ().(),
: ,
userId, : , ip,
: , : err.,
});
err;
}
}
Output
- Scoped API keys per service/environment
- Built-in PII redaction via
redact parameter
- Temporary keys for client-side (browser/mobile)
- Key rotation with validation and cleanup
- SSRF-safe audio URL validation
- Structured audit logging
Error Handling
| Issue | Cause | Solution |
|---|
| 403 after scoping | Key missing required scope | Add scope in Console (e.g., listen) |
| Temp key expired | TTL too short | Increase time_to_live_in_seconds |
| Rotation broke service | New key not propagated | Use overlap period — both keys active |
| Redaction missed PII | Wrong redact option | Use redact: ['pci', 'ssn', 'numbers'] |
Resources