Observed since 20 Oct, 20:44 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Official 60db SDK for Node.js
Official 60db SDK for JavaScript/TypeScript
npm install 60db-service
import { SixtyDBClient } from '60db-service';
const client = new SixtyDBClient('your-api-key');
Basic TTS (returns ArrayBuffer):
const audio = await client.textToSpeech({
text: 'Hello, world!',
voice_id: 'your-voice-id',
speed: 1, // 0.5 to 2.0 (default 1)
stability: 50, // 0 to 100 (default 50)
similarity: 75, // 0 to 100 (default 75)
sample_rate: 24000
});
Streaming TTS (real-time chunks):
await client.textToSpeechStream(
{
text: 'This is a streaming example.',
voice_id: 'your-voice-id',
sample_rate: 24000
},
{
onChunk: (chunkIndex, totalBytes) => {
console.log(`Progress: ${chunkIndex} chunks, ${totalBytes} bytes`);
},
onRawChunk: (pcmBytes, sampleRate) => {
console.log(`Received: ${pcmBytes.length} bytes at ${sampleRate}Hz`);
},
onComplete: (wavUrl, sampleRate, totalPcmBytes) => {
console.log(`Complete! WAV: ${wavUrl}`);
console.log(`Duration: ${totalPcmBytes / (sampleRate * 2)} seconds`);
},
onError: (error) => {
console.error('Error:', error);
}
}
);
Non-streaming (simple):
const response = await client.chat.completions.create({
model: 'qcall/slm-3b-int4',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'What is 60db?' }
],
stream: false
});
console.log(response.choices[0].message.content);
Streaming (real-time):
const stream = await client.chat.completions.create({
model: 'qcall/slm-3b-int4',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Tell me a joke' }
],
stream: true
});
for await (const chunk of stream) {
if (chunk.choices?.[0]?.delta?.content) {
process.stdout.write(chunk.choices[0].delta.content);
}
}
Custom parameters:
const response = await client.chat.completions.create({
model: 'qcall/slm-3b-int4',
messages: [
{ role: 'user', content: 'Explain AI briefly' }
],
top_k: 50, // Default: 100
chat_template_kwargs: {
enable_thinking: true // Enable thinking mode
},
stream: false
});
// List all voices
const voices = await client.getVoices();
console.log('Total voices:', voices.data.cloned_voices.length);
// Get specific voice
const voice = await client.getVoice(voiceId);
const languages = await client.getLanguages();
// Returns: [{ language_id: 'en', name: 'English' }, ...]
const result = await client.speechToText(audioFile, {
language: 'en'
});
Free during beta (no credit check, nothing charged). Each create() call
makes 1 song, and only one generation runs at a time per workspace
(a second create() while one is running returns 429). A song takes about
1.5-2.5 minutes.
// Create a song (async)
const { data } = await client.music.create({
mode: 'simple',
prompt: 'An upbeat pop song about summer road trips',
});
const songId = data.songs[0].id;
// Poll until it's done
const song = await client.music.waitForCompletion(songId);
console.log(song.status, song.audio_url);
// Download the MP3 permanently
const bytes = await client.music.download(songId);
// List / manage songs
await client.music.list({ limit: 10, status: 'ready' });
await client.music.get(songId);
await client.music.update(songId, { liked: true });
await client.music.delete(songId);
// Voices
const { data: voices } = await client.music.listVoices(); // { catalog, mine }
await client.music.createVoice({ name: 'My Voice', file: audioBlob });
await client.music.health();
Scores content against a rubric you define, and parses conversational turns. It is a classifier, not a generator — every answer is a probability distribution over your own options, so results are comparable and sortable.
// Score a call. Three question types: choice, score, noul (true/false).
const run = await client.judge.evaluate({
state: callTranscript,
questions: {
tone: { type: 'choice', instructions: 'How did the agent come across?',
criteria: { professional: 'Calm and courteous',
dismissive: 'Brushes the caller off',
unknown: 'Not clear' } }, // always include an escape hatch
satisfaction: { type: 'score', criteria: ['Angry', 'Neutral', 'Happy'] },
resolved: { type: 'noul', instructions: 'Was it solved?' },
},
});
run.answers.tone.choice // 'dismissive'
run.answers.tone.probabilities // { dismissive: 0.86, professional: 0.11, unknown: 0.03 }
run.answers.satisfaction.score // 1.87 — weighted position on your ladder, not an index
run.answers.resolved.noul // 0.28 — raw probability of true, NOT a confidence
run.min_confidence // lowest across the answers; under 0.85 wants a human
// Save a rubric once, then run it by id.
const rubric = await client.judge.rubrics.create({ name: 'Call QA', questions });
await client.judge.evaluate({ state: transcript, rubric_id: rubric.id });
// Parse one live turn: intent, operation and entity spans.
const turn = await client.judge.extract({
text: 'move my 10am appointment to friday',
schema: {
intents: { booking: 'Wants to change a booking' },
operations: { reschedule: 'Move it to a new time' },
entities: { date: 'A calendar date', time: 'A clock time' },
},
});
turn.intent.label // 'booking'
turn.entities // [{ label: 'date', text: 'friday', start: 28, end: 34, confidence: 0.8 }]
// The review queue — runs the judge was under 85% sure about.
const { runs } = await client.judge.runs.list({ needs_review: true });
await client.judge.models(); // authoritative model list — don't hardcode
await client.judge.usage('current_month'); // spend and run counts
Entity offsets are Unicode code points, not UTF-16 indices — slice with
Array.from(text).slice(start, end) or an emoji earlier in the turn shifts
every span after it.
Pricing: per input token. The upstream re-encodes the shared state once per question, so a 10-question rubric costs ten times the transcript. Context is 8K tokens per question. A failed run is refunded automatically.
Not free: a number costs $6.00/month, calls cost $0.003/min. Most
calls need a finished trunk setup (dashboard) and approved dialer KYC
(also done in the dashboard, not exposed by this SDK). calls.reserve()
does not dial — it only pre-authorizes a call id for a SIP client
(softphone/PBX) to use.
await client.dialer.status();
// Numbers
await client.dialer.numbers.search();
// Buying requires approved KYC (done in the 60db app) — otherwise 403
// DIALER_KYC_REQUIRED / DIALER_KYC_PENDING / DIALER_KYC_REJECTED.
// Charges $6.00 now, then $6.00/month.
await client.dialer.numbers.buy({ assign_for: 'sip' });
await client.dialer.numbers.list();
await client.dialer.numbers.setCallerId('+919876543210');
await client.dialer.numbers.release('+919876543210');
// Calls
await client.dialer.calls.list({ limit: 20 });
// Reserves a call id — does NOT dial; a SIP client places the actual call.
await client.dialer.calls.reserve({ to: '+919876543210', from: '+911234567890', sipCallId: 'uuid' });
await client.dialer.calls.live();
// Recordings
await client.dialer.recordings.list();
await client.dialer.recordings.setEnabled(true);
// Billing
await client.dialer.billing.usage({ limit: 20 });
await client.dialer.billing.subscriptions();
// Webhooks
await client.dialer.webhooks.create({ name: 'My hook', url: 'https://example.com/hook', event_types: ['call.completed'] });
await client.dialer.webhooks.list();
const client = new SixtyDBClient({
apiKey: 'your-api-key',
baseUrl: 'https://api.60db.ai' // Optional, default shown
});
| Method | Description |
|---|---|
textToSpeech(params) | Convert text to speech (returns ArrayBuffer) |
textToSpeechStream(params, callbacks) | Stream text to speech with callbacks |
chat.completions.create(params) | Chat completions (LLM) with streaming support |
getVoices() | List all available voices |
getVoice(id) | Get specific voice details |
getLanguages() | Get supported languages |
speechToText(audio, options) | Transcribe audio to text |
music.create(params) | Create a song (async, free during beta) |
music.list(params) | List songs |
music.get(id) | Get a song (includes lyrics) |
music.update(id, params) | Update liked/played flags |
music.delete(id) | Move a song to trash |
music.download(id) | Download the MP3 (ArrayBuffer) |
music.waitForCompletion(id, opts) | Poll until the song succeeds/fails |
music.listVoices() | List catalog + saved voices |
music.createVoice(params) | Save a voice from an audio clip |
music.health() | Song service status |
judge.evaluate(params) | Score content against inline questions or a saved rubric |
judge.extract(params) | Classify one turn: intent, operation, entity spans |
judge.models() | Model names evaluate accepts (authoritative) |
judge.usage(period) | Judge spend and saved-run counts |
judge.health() | Judge upstream readiness and model revisions |
judge.rubrics.* | List, create, get, update, delete saved rubrics |
judge.runs.* | List (incl. needs_review queue), get, delete runs |
dialer.status() | Dialer provisioning status |
dialer.numbers.* | Search/buy/list/release numbers, set caller id |
dialer.calls.* | Call history, get, live, line status, reserve |
dialer.recordings.* | List, playback URL, transcript, delete, enable |
dialer.billing.* | Subscriptions, usage |
dialer.webhooks.* | List, create, update, delete, rotate secret, test, deliveries |
MIT
Download counts and popularity will appear here as the collector receives them.
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 20:44 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:b8adc34fa939a4d3a0a85751cae15aa063315b8576d128c1da77dab15d613c5e