Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.camb.ai/llms.txt

Use this file to discover all available pages before exploring further.

The official TypeScript SDK for Camb.ai provides convenient access to text-to-speech, dubbing, translation, transcription, audio separation, voice cloning, and audio generation. Every method returns a promise, the package ships as ES modules, and saveStreamToFile handles audio output in one call.

Installation

npm install @camb-ai/sdk
yarn add @camb-ai/sdk
pnpm add @camb-ai/sdk
Requires Node.js 18 or higher. The package is published as @camb-ai/sdk and ships as ES modules.

Authentication

Get your API key from Camb.ai Studio and read it from process.env.CAMB_API_KEY so it never appears in source control. Every API call on CambClient returns a promise. There is no synchronous client: wrap usage in async functions and use await throughout.
import { CambClient } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});
Use dotenv to load CAMB_API_KEY from a .env file during local development.

Quick Start

Streaming TTS returns binary data that saveStreamToFile writes to disk. await client.textToSpeech.tts(...) resolves to a stream object the helper understands.
import { CambClient, CambApi, saveStreamToFile } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

async function main() {
  const stream = await client.textToSpeech.tts({
    text: "Hello from Camb.ai.",
    language: CambApi.CreateStreamTtsRequestPayload.Language.EnUs,
    voice_id: 147320,
    speech_model: CambApi.CreateStreamTtsRequestPayload.SpeechModel.MarsFlash,
    output_configuration: { format: "wav" },
  });

  await saveStreamToFile(stream, "output.wav");
  console.log("Audio saved to output.wav");
}

void main();

Models

Camb.ai offers MARS models tuned for different quality and latency requirements. Pass speech_model using CambApi.CreateStreamTtsRequestPayload.SpeechModel. If you omit it, the API applies its default model.
ModelSample RateBest For
mars-8.1-flash-beta48 kHzFaster MARS 8.1 generation; same quality improvements as mars-8.1-pro-beta
mars-8.1-pro-beta48 kHzImproved pronunciation, expressiveness, and prosody over mars-pro
mars-flash22.05 kHzLow-latency real-time applications and conversational AI
mars-pro48 kHzHigh-fidelity audio production and long-form content
mars-instruct22.05 kHzFine-grained tone and style control via text instructions
The tab snippets below assume the same client and CambApi imports as in Quick Start.
const stream = await client.textToSpeech.tts({
  text: "Hey, I can respond much faster.",
  language: CambApi.CreateStreamTtsRequestPayload.Language.EnUs,
  voice_id: 147320,
  speech_model: CambApi.CreateStreamTtsRequestPayload.SpeechModel.MarsFlash,
  output_configuration: { format: "wav" },
});
Best for: Voice agents, real-time applicationsSample rate: 22.05 kHz

Voices

List voices

listVoices() returns every voice available to your account, including library voices and custom clones. Use the returned id (or equivalent field from the typed response) as voice_id in TTS calls.
import { CambClient } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

async function main() {
  const voices = await client.voiceCloning.listVoices();
  for (const voice of voices) {
    console.log(voice);
  }
}

void main();

Create a custom voice

Upload a short reference clip with a display name and gender. Set enhance_audio when the recording may contain noise so the API can preprocess the clip before cloning.
import { createReadStream } from "node:fs";
import { CambClient, CambApi } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

async function main() {
  const result = await client.voiceCloning.createCustomVoice({
    voice_name: "My Custom Voice",
    gender: 1 as CambApi.Gender,
    file: createReadStream("reference.wav"),
    description: "Warm and conversational.",
    enhance_audio: true,
  });

  console.log(result);
}

void main();

Language support

TTS requests use string locale values through CambApi.CreateStreamTtsRequestPayload.Language (for example Language.EnUs maps to "en-us"). See Language Support for the full per-model locale list. Dubbing, translation, transcription, and several other endpoints use the numeric CambApi.Languages enum so the wire format matches the REST API exactly.
import { CambApi } from "@camb-ai/sdk";

console.log(CambApi.CreateStreamTtsRequestPayload.Language.EnUs);
console.log(CambApi.Languages.EN_US, CambApi.Languages.HI_IN, CambApi.Languages.FR_FR);
Fetch the canonical list of supported languages from the API when you need to populate UI or validate user input.
import { CambClient } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

async function main() {
  const source = await client.languages.getSourceLanguages();
  const target = await client.languages.getTargetLanguages();
  console.log(source, target);
}

void main();
Languages supported per model are listed at MARS Models.

Dubbing

The dubbing client translates the audio track of a remote video and resynthesizes speech with cloned voices. In TypeScript the entry point is endToEndDubbing, not the Python name create_dub. You submit a job, poll getEndToEndDubbingStatus until status is success, then read URLs from getDubbedRunInfo.
import { CambClient, CambApi } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function main() {
  const submitted = await client.dub.endToEndDubbing({
    video_url: "https://example.com/video.mp4",
    source_language: CambApi.Languages.EN_US,
    target_language: CambApi.Languages.HI_IN,
  });

  const taskId = submitted.task_id;
  if (!taskId) {
    throw new Error("No task_id returned from endToEndDubbing");
  }

  let runId: number | null | undefined;
  while (true) {
    const status = await client.dub.getEndToEndDubbingStatus({ task_id: taskId });
    if (status.status === CambApi.TaskStatus.Success) {
      runId = status.run_id ?? undefined;
      break;
    }
    await sleep(5000);
  }

  if (runId == null) {
    throw new Error("No run_id after success");
  }

  const info = await client.dub.getDubbedRunInfo({ run_id: runId });
  console.log(info);
}

void main();
To dub into several targets in one job, pass target_languages instead of target_language.

Get transcript

After a successful run, getDubbedRunTranscript returns transcript text keyed by field name for the requested target language.
const transcript = await client.dub.getDubbedRunTranscript({
  run_id: runId,
  language: CambApi.Languages.HI_IN,
});
console.log(transcript);

Translation

createTranslation accepts an array of strings and returns translations in the same order. The workflow matches dubbing: create a task, poll getTranslationTaskStatus, then call getTranslationResult with the run_id from the status payload.
import { CambClient, CambApi } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function main() {
  const submitted = await client.translation.createTranslation({
    texts: ["Hello, how are you?", "Welcome to Camb.ai."],
    source_language: CambApi.Languages.EN_US,
    target_language: CambApi.Languages.FR_FR,
  });

  const taskId = submitted.task_id;
  if (!taskId) {
    throw new Error("No task_id returned from createTranslation");
  }

  let runId: number | null | undefined;
  while (true) {
    const status = await client.translation.getTranslationTaskStatus({ task_id: taskId });
    if (status.status === CambApi.TaskStatus.Success) {
      runId = status.run_id ?? undefined;
      break;
    }
    await sleep(2000);
  }

  if (runId == null) {
    throw new Error("No run_id after success");
  }

  const result = await client.translation.getTranslationResult({ run_id: runId });
  for (const text of result.texts ?? []) {
    console.log(text);
  }
}

void main();
For streaming translation over a single string, see client.translation.translationStream in the generated client types.

Transcription

Send either media_url or a local media_file. Poll until the task succeeds, then request structured text (optionally with word-level timestamps) from getTranscriptionResult.
import { createReadStream } from "node:fs";
import { CambClient, CambApi } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function main() {
  const submitted = await client.transcription.createTranscription({
    language: CambApi.Languages.EN_US,
    media_url: "https://example.com/audio.mp3",
  });

  const taskId = submitted.task_id;
  if (!taskId) {
    throw new Error("No task_id returned from createTranscription");
  }

  let runId: number | null | undefined;
  while (true) {
    const status = await client.transcription.getTranscriptionTaskStatus({ task_id: taskId });
    if (status.status === CambApi.TaskStatus.Success) {
      runId = status.run_id ?? undefined;
      break;
    }
    await sleep(3000);
  }

  if (runId == null) {
    throw new Error("No run_id after success");
  }

  const result = await client.transcription.getTranscriptionResult({
    run_id: runId,
    word_level_timestamps: true,
  });
  console.log(result);
}

void main();
To transcribe a local file, pass a stream or buffer-compatible upload as media_file instead of media_url.

Audio separation

Audio separation splits a mixed recording into stems. Upload media_file, poll getAudioSeparationStatus, then read stem metadata from getAudioSeparationRunInfo.
import { createReadStream } from "node:fs";
import { CambClient, CambApi } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function main() {
  const submitted = await client.audioSeparation.createAudioSeparation({
    media_file: createReadStream("track.mp3"),
  });

  const taskId = submitted.task_id;
  if (!taskId) {
    throw new Error("No task_id returned from createAudioSeparation");
  }

  let runId: number | null | undefined;
  while (true) {
    const status = await client.audioSeparation.getAudioSeparationStatus({ task_id: taskId });
    if (status.status === CambApi.TaskStatus.Success) {
      runId = status.run_id ?? undefined;
      break;
    }
    await sleep(3000);
  }

  if (runId == null) {
    throw new Error("No run_id after success");
  }

  const info = await client.audioSeparation.getAudioSeparationRunInfo({ run_id: runId });
  console.log(info);
}

void main();

Text-to-voice

Text-to-voice synthesizes a new voice timbre from a written description and sample line. The pipeline is asynchronous: create the job, poll status, then inspect getTextToVoiceResult for preview URLs and identifiers you can feed back into TTS.
import { CambClient, CambApi } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function main() {
  const submitted = await client.textToVoice.createTextToVoice({
    text: "A confident narrator introducing a documentary.",
    voice_description:
      "Deep, measured baritone with a slight gravel. Calm and authoritative.",
  });

  const taskId = submitted.task_id;
  if (!taskId) {
    throw new Error("No task_id returned from createTextToVoice");
  }

  let runId: number | null | undefined;
  while (true) {
    const status = await client.textToVoice.getTextToVoiceStatus({ task_id: taskId });
    if (status.status === CambApi.TaskStatus.Success) {
      runId = status.run_id ?? undefined;
      break;
    }
    await sleep(3000);
  }

  if (runId == null) {
    throw new Error("No run_id after success");
  }

  const result = await client.textToVoice.getTextToVoiceResult({ run_id: runId });
  console.log(result);
}

void main();
Inspect the getTextToVoiceResult payload for preview URLs and the voice_id you should pass into textToSpeech.tts for production speech.

Text-to-audio

Text-to-audio generates sound effects or ambient beds from a prompt. The create call returns a task_id. After polling succeeds, getTextToAudioResult returns streaming audio that saveStreamToFile can persist.
import { CambClient, CambApi, saveStreamToFile } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function main() {
  const submitted = await client.textToAudio.createTextToAudio({
    prompt: "Heavy rain on a tin roof at night with distant thunder.",
    duration: 15,
    audio_type: CambApi.TextToAudioType.Sound,
  });

  const taskId = submitted.task_id;
  if (!taskId) {
    throw new Error("No task_id returned from createTextToAudio");
  }

  let runId: number | null | undefined;
  while (true) {
    const status = await client.textToAudio.getTextToAudioStatus({ task_id: taskId });
    if (status.status === CambApi.TaskStatus.Success) {
      runId = status.run_id ?? undefined;
      break;
    }
    await sleep(3000);
  }

  if (runId == null) {
    throw new Error("No run_id after success");
  }

  const stream = await client.textToAudio.getTextToAudioResult({ run_id: runId });
  await saveStreamToFile(stream, "soundscape.mp3");
  console.log("Saved to soundscape.mp3");
}

void main();

Stories

The story endpoint ingests a text or Word document, builds narration, and returns run metadata when processing completes. Pass source_language as CambApi.Languages and stream the document as file.
import { createReadStream } from "node:fs";
import { CambClient, CambApi } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function main() {
  const submitted = await client.story.createStory({
    file: createReadStream("story.pdf"),
    source_language: CambApi.Languages.EN_US,
    title: "My Story",
  });

  const taskId = submitted.task_id;
  if (!taskId) {
    throw new Error("No task_id returned from createStory");
  }

  let runId: number | null | undefined;
  while (true) {
    const status = await client.story.getStoryStatus({ task_id: taskId });
    if (status.status === CambApi.TaskStatus.Success) {
      runId = status.run_id ?? undefined;
      break;
    }
    await sleep(5000);
  }

  if (runId == null) {
    throw new Error("No run_id after success");
  }

  const info = await client.story.getStoryRunInfo({ run_id: runId });
  console.log(info);
}

void main();

Translated TTS

Translated TTS translates text and renders speech in the target language in one pipeline. The TypeScript client exposes createTranslatedTts and getTranslatedTtsTaskStatus. When the status reports success, inspect the status object and the API reference for URLs or identifiers your integration should download.
import { CambClient, CambApi } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function main() {
  const submitted = await client.translatedTts.createTranslatedTts({
    text: "Good morning, welcome to our service.",
    voice_id: 147320,
    source_language: CambApi.Languages.EN_US,
    target_language: CambApi.Languages.HI_IN,
  });

  const taskId = submitted.task_id;
  if (!taskId) {
    throw new Error("No task_id returned from createTranslatedTts");
  }

  while (true) {
    const status = await client.translatedTts.getTranslatedTtsTaskStatus({ task_id: taskId });
    if (status.status === CambApi.TaskStatus.Success) {
      console.log(status);
      break;
    }
    await sleep(3000);
  }
}

void main();

Dictionaries

Dictionaries store glossary rows that dubbing and translation jobs can consume automatically. Upload bulk CSV files or manage individual terms through the JSON endpoints.

List dictionaries

import { CambClient } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

async function main() {
  const dictionaries = await client.dictionaries.getDictionaries();
  for (const d of dictionaries) {
    console.log(d.id, d.name);
  }
}

void main();

Create from file

import { createReadStream } from "node:fs";
import { CambClient } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

async function main() {
  await client.dictionaries.createDictionaryFromFile({
    dictionary_file: createReadStream("terms.csv"),
    dictionary_name: "Product Terms",
    dictionary_description: "Brand-specific terminology for our product line.",
  });
}

void main();

Manage terms

Each translation row pairs a string with a CambApi.Languages value. Use numeric dictionary and term identifiers returned by previous API calls.
import { CambClient, CambApi } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

async function main() {
  await client.dictionaries.addTermToDictionary({
    dictionary_id: 123,
    translations: [
      { translation: "कैम्ब.एआई", language: CambApi.Languages.HI_IN },
    ],
  });

  await client.dictionaries.deleteDictionaryTerm({
    dictionary_id: 123,
    term_id: 456,
  });

  await client.dictionaries.deleteDictionary({ dictionary_id: 123 });
}

void main();

Folders

List or create folders with client.folders.listFolders({}) and client.folders.createFolder({ folder_name: "..." }) when you need to organize runs in Studio.

Error handling

Failed HTTP responses throw CambApiError. Validation failures surface as CambApi.UnprocessableEntityError, which extends CambApiError and carries the parsed body.
import { CambClient, CambApi, CambApiError, saveStreamToFile } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
});

async function main() {
  try {
    const stream = await client.textToSpeech.tts({
      text: "Hello world.",
      language: CambApi.CreateStreamTtsRequestPayload.Language.EnUs,
      voice_id: 147320,
      output_configuration: { format: "wav" },
    });
    await saveStreamToFile(stream, "out.wav");
  } catch (error) {
    if (error instanceof CambApi.UnprocessableEntityError) {
      console.error("Validation error", error.statusCode, error.body);
    } else if (error instanceof CambApiError) {
      console.error("API error", error.statusCode, error.body);
    } else {
      console.error(error);
    }
  }
}

void main();
Pass requestOptions on any call to override timeouts or retries for that request.
await client.textToSpeech.tts(
  {
    text: "Hello world.",
    language: CambApi.CreateStreamTtsRequestPayload.Language.EnUs,
    voice_id: 147320,
    output_configuration: { format: "wav" },
  },
  { timeoutInSeconds: 30, maxRetries: 3 },
);

Custom provider

Self-hosted MARS on Baseten reuses textToSpeech.tts, but you must construct CambClient with ttsProvider: "baseten" and supply providerParams. Baseten also requires base64 reference_audio and a reference_language entry inside additional_body_parameters on each TTS request. See Custom Cloud Providers for deployment details.

Baseten

import { readFileSync } from "node:fs";
import { CambClient, CambApi, CambApiError, saveStreamToFile } from "@camb-ai/sdk";

const client = new CambClient({
  apiKey: process.env.CAMB_API_KEY,
  ttsProvider: "baseten",
  providerParams: {
    api_key: process.env.BASETEN_API_KEY,
    mars_pro_url: process.env.BASETEN_URL,
  },
});

async function main() {
  try {
    const referenceAudio = readFileSync("reference.wav").toString("base64");
    const payload = {
      text: "Hello from a self-hosted MARS deployment.",
      language: CambApi.CreateStreamTtsRequestPayload.Language.EnUs,
      voice_id: 1,
      speech_model: CambApi.CreateStreamTtsRequestPayload.SpeechModel.MarsPro,
      output_configuration: { format: "mp3" },
      additional_body_parameters: {
        reference_audio: referenceAudio,
        reference_language: CambApi.CreateStreamTtsRequestPayload.Language.EnUs,
      },
    } as CambApi.CreateStreamTtsRequestPayload & {
      additional_body_parameters: Record<string, string>;
    };

    const stream = await client.textToSpeech.tts(payload, { timeoutInSeconds: 300 });
    await saveStreamToFile(stream, "output.mp3");
  } catch (error) {
    if (error instanceof CambApiError) {
      console.error(error.statusCode, error.body);
    } else {
      console.error(error);
    }
  }
}

void main();
Baseten validates reference_audio and reference_language. You can supply only ttsProvider and providerParams if you never call the default Camb.ai API, or include apiKey when you use both hosted and Baseten routes in one process.

Next steps

https://mintcdn.com/cambai/2LvnefIkletroPxv/images/pipecat-orange.svg?fit=max&auto=format&n=2LvnefIkletroPxv&q=85&s=40cf8e001b8cadc8a4c3c557dea603d5

Voice Agents

Build real-time voice agents with Pipecat
https://mintcdn.com/cambai/2LvnefIkletroPxv/images/livekit-orange.svg?fit=max&auto=format&n=2LvnefIkletroPxv&q=85&s=c750fcee9b1de69e3c1d0d6ec7eb6b3f

LiveKit Integration

Create voice agents with LiveKit

API Reference

Explore the full TTS API

Voice Library

Browse available voices

Resources