> ## 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.

# TypeScript SDK

> Install and use the TypeScript SDK for Node.js 18+ with a promise-based API

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

```bash theme={null}
npm install @camb-ai/sdk
```

```bash theme={null}
yarn add @camb-ai/sdk
```

```bash theme={null}
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](https://studio.camb.ai) 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.

```typescript theme={null}
import { CambClient } from "@camb-ai/sdk";

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

<Tip>
  Use [dotenv](https://www.npmjs.com/package/dotenv) to load `CAMB_API_KEY` from a `.env` file during local development.
</Tip>

***

## Quick Start

Streaming TTS returns binary data that `saveStreamToFile` writes to disk. `await client.textToSpeech.tts(...)` resolves to a stream object the helper understands.

```typescript theme={null}
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.

| Model                 | Sample Rate | Best For                                                                     |
| --------------------- | ----------- | ---------------------------------------------------------------------------- |
| `mars-8.1-flash-beta` | 48 kHz      | Faster MARS 8.1 generation; same quality improvements as `mars-8.1-pro-beta` |
| `mars-8.1-pro-beta`   | 48 kHz      | Improved pronunciation, expressiveness, and prosody over `mars-pro`          |
| `mars-flash`          | 22.05 kHz   | Low-latency real-time applications and conversational AI                     |
| `mars-pro`            | 48 kHz      | High-fidelity audio production and long-form content                         |
| `mars-instruct`       | 22.05 kHz   | Fine-grained tone and style control via text instructions                    |

The tab snippets below assume the same `client` and `CambApi` imports as in [Quick Start](#quick-start).

<Tabs>
  <Tab title="MARS Flash">
    ```typescript theme={null}
    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 applications

    **Sample rate:** 22.05 kHz
  </Tab>

  <Tab title="MARS Pro">
    ```typescript theme={null}
    const stream = await client.textToSpeech.tts({
      text: "High-fidelity audio for production use.",
      language: CambApi.CreateStreamTtsRequestPayload.Language.EnUs,
      voice_id: 147320,
      speech_model: CambApi.CreateStreamTtsRequestPayload.SpeechModel.MarsPro,
      output_configuration: { format: "wav" },
    });
    ```

    **Best for:** Audio production, dubbing, long-form content

    **Sample rate:** 48 kHz
  </Tab>

  <Tab title="MARS Instruct">
    ```typescript theme={null}
    const stream = await client.textToSpeech.tts({
      text: "[warm, friendly] Great to meet you!",
      language: CambApi.CreateStreamTtsRequestPayload.Language.EnUs,
      voice_id: 147320,
      speech_model: CambApi.CreateStreamTtsRequestPayload.SpeechModel.MarsInstruct,
      user_instructions: "Speak warmly and with enthusiasm.",
      output_configuration: { format: "wav" },
    });
    ```

    **Best for:** Fine-grained control over tone and delivery

    **Sample rate:** 22.05 kHz
  </Tab>
</Tabs>

***

## 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.

```typescript theme={null}
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.

```typescript theme={null}
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](/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.

```typescript theme={null}
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.

```typescript theme={null}
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](/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`.

```typescript theme={null}
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.

```typescript theme={null}
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.

```typescript theme={null}
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();
```

<Note>
  For streaming translation over a single string, see `client.translation.translationStream` in the generated client types.
</Note>

***

## 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`.

```typescript theme={null}
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`.

```typescript theme={null}
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.

```typescript theme={null}
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();
```

<Note>
  Inspect the `getTextToVoiceResult` payload for preview URLs and the `voice_id` you should pass into `textToSpeech.tts` for production speech.
</Note>

***

## 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.

```typescript theme={null}
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`.

```typescript theme={null}
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](/api-reference) for URLs or identifiers your integration should download.

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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.

```typescript theme={null}
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`.

```typescript theme={null}
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.

```typescript theme={null}
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](/custom-cloud-providers) for deployment details.

### Baseten

```typescript theme={null}
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();
```

<Note>
  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.
</Note>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Voice Agents" icon="https://mintcdn.com/cambai/2LvnefIkletroPxv/images/pipecat-orange.svg?fit=max&auto=format&n=2LvnefIkletroPxv&q=85&s=40cf8e001b8cadc8a4c3c557dea603d5" href="/integrations/pipecat" width="24" height="24" data-path="images/pipecat-orange.svg">
    Build real-time voice agents with Pipecat
  </Card>

  <Card title="LiveKit Integration" icon="https://mintcdn.com/cambai/2LvnefIkletroPxv/images/livekit-orange.svg?fit=max&auto=format&n=2LvnefIkletroPxv&q=85&s=c750fcee9b1de69e3c1d0d6ec7eb6b3f" href="/integrations/livekit" width="24" height="24" data-path="images/livekit-orange.svg">
    Create voice agents with LiveKit
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/endpoint/create-tts-stream">
    Explore the full TTS API
  </Card>

  <Card title="Voice Library" icon="speech" href="/api-reference/endpoint/list-voices">
    Browse available voices
  </Card>
</CardGroup>

***

## Resources

* [GitHub: camb-ai/cambai-node-sdk](https://github.com/Camb-ai/cambai-node-sdk)
* [SDK examples](https://github.com/Camb-ai/cambai-node-sdk/tree/main/examples)
* [API Reference](/api-reference/endpoint/create-tts-stream)
