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

# PHP SDK

> Install and use the Camb.ai PHP SDK with PHP 8.1+ and Composer

The official PHP SDK for Camb.ai provides access to text-to-speech, dubbing, translation, transcription, audio separation, voice cloning, and audio generation through `CambAIClient` for common workflows and generated API classes for the rest of the HTTP surface. The SDK is synchronous: call APIs from your PHP process or queue workers and poll until asynchronous jobs finish.

## Installation

Requires PHP 8.1 or later and Composer. Add the Camb.ai repository and package to your `composer.json`:

```json theme={null}
{
  "repositories": [
    {
      "type": "vcs",
      "url": "https://github.com/Camb-ai/cambai-php-sdk.git"
    }
  ],
  "require": {
    "camb-ai/cambai-php-sdk": "*@dev"
  }
}
```

Then run:

```bash theme={null}
composer install
```

***

## Authentication

Get your API key from [Camb.ai Studio](https://studio.camb.ai) and read it from an environment variable so it never appears in source control.

`CambAIClient` wires TTS (via a provider), Text-to-Voice, Text-to-Audio, and Dubbing. Other endpoints use generated `*Api` classes with a shared `Configuration` and Guzzle client. There is no separate async client in PHP: run blocking calls in requests or workers.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\CambAIClient;
use Camb\Ai\Configuration;
use GuzzleHttp\Client;

$client = new CambAIClient(getenv('CAMB_API_KEY'));

$config = Configuration::getDefaultConfiguration()->setApiKey('x-api-key', getenv('CAMB_API_KEY'));
$httpClient = new Client(['timeout' => 300.0]);
```

<Tip>
  Load `CAMB_API_KEY` from a `.env` file during local development with [vlucas/phpdotenv](https://github.com/vlucas/phpdotenv) or your framework’s env layer.
</Tip>

The sections below that use `$httpClient` and `$config` assume you built them as shown. `CambAIClient` already applies the same API key and timeout internally.

***

## Quick Start

Streaming TTS returns a readable stream. `DefaultTtsProvider::tts()` maps to the streaming endpoint. Read the stream in chunks and write them to disk.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\CambAIClient;
use Camb\Ai\Model\CreateStreamTTSRequestPayload;
use Camb\Ai\Model\StreamTTSOutputConfiguration;

$client = new CambAIClient(getenv('CAMB_API_KEY'));

$outputConfig = new StreamTTSOutputConfiguration();
$outputConfig->setFormat('wav');

$payload = new CreateStreamTTSRequestPayload();
$payload->setText('Hello from Camb.ai.');
$payload->setVoiceId(147320);
$payload->setLanguage(CreateStreamTTSRequestPayload::LANGUAGE_EN_US);
$payload->setSpeechModel(CreateStreamTTSRequestPayload::SPEECH_MODEL_MARS_FLASH);
$payload->setOutputConfiguration($outputConfig);

$audioStream = $client->tts()->tts($payload);
$outputFile = 'output.wav';
$file = fopen($outputFile, 'w');
if ($file) {
    while (!$audioStream->eof()) {
        fwrite($file, $audioStream->fread(8192));
    }
    fclose($file);
}
```

### Non-streaming TTS

If you need a task-based TTS job instead of streaming, call `createTts()` on the same provider, poll task status, then fetch run metadata.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\CambAIClient;
use Camb\Ai\Model\CreateTTSRequestPayload;
use Camb\Ai\Model\Languages;
use Camb\Ai\Model\OrchestratorPipelineResult;
use Camb\Ai\Model\TaskStatus;

$client = new CambAIClient(getenv('CAMB_API_KEY'));

$payload = new CreateTTSRequestPayload();
$payload->setText('Hello from Camb.ai.');
$payload->setVoiceId(147320);
$payload->setLanguage(Languages::EN_US);
$payload->setSpeechModel('mars-flash');

$created = $client->tts()->createTts($payload);
$taskId = $created->getTaskId();

while (true) {
    /** @var OrchestratorPipelineResult $status */
    $status = $client->textToSpeech->getTtsResultTtsTaskIdGet($taskId);
    if ($status->getStatus() === TaskStatus::SUCCESS) {
        $runId = $status->getRunId();
        $ttsRunInfo = $client->textToSpeech->getTtsRunInfoTtsResultRunIdGet($runId);
        break;
    }
    if ($status->getStatus() === TaskStatus::ERROR) {
        break;
    }
    sleep(2);
}
```

When the loop exits with success, `$ttsRunInfo` holds download URLs and metadata for the synthesized audio.

***

## Models

Camb.ai offers MARS models tuned for different quality and latency requirements. Pass `speech_model` on streaming payloads using `CreateStreamTTSRequestPayload` constants or string literals. 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 reuse `$client` from [Quick Start](#quick-start).

<Tabs>
  <Tab title="MARS Flash">
    ```php theme={null}
    $payload = new CreateStreamTTSRequestPayload();
    $payload->setText('Hey, I can respond much faster.');
    $payload->setVoiceId(147320);
    $payload->setLanguage(CreateStreamTTSRequestPayload::LANGUAGE_EN_US);
    $payload->setSpeechModel(CreateStreamTTSRequestPayload::SPEECH_MODEL_MARS_FLASH);
    $payload->setOutputConfiguration($outputConfig);

    $stream = $client->tts()->tts($payload);
    ```
  </Tab>

  <Tab title="MARS Pro">
    ```php theme={null}
    $payload = new CreateStreamTTSRequestPayload();
    $payload->setText('High-fidelity audio for production use.');
    $payload->setVoiceId(147320);
    $payload->setLanguage(CreateStreamTTSRequestPayload::LANGUAGE_EN_US);
    $payload->setSpeechModel(CreateStreamTTSRequestPayload::SPEECH_MODEL_MARS_PRO);
    $payload->setOutputConfiguration($outputConfig);

    $stream = $client->tts()->tts($payload);
    ```
  </Tab>

  <Tab title="MARS Instruct">
    ```php theme={null}
    $payload = new CreateStreamTTSRequestPayload();
    $payload->setText('[warm, friendly] Great to meet you!');
    $payload->setVoiceId(147320);
    $payload->setLanguage(CreateStreamTTSRequestPayload::LANGUAGE_EN_US);
    $payload->setSpeechModel(CreateStreamTTSRequestPayload::SPEECH_MODEL_MARS_INSTRUCT);
    $payload->setUserInstructions('Speak warmly and with enthusiasm.');
    $payload->setOutputConfiguration($outputConfig);

    $stream = $client->tts()->tts($payload);
    ```
  </Tab>
</Tabs>

For beta models, pass the string on the payload when no constant exists:

```php theme={null}
$payload->setSpeechModel('mars-8.1-flash-beta');
```

***

## TTS Options

Streaming TTS uses `CreateStreamTTSRequestPayload`. Set the core request fields plus optional controls before calling `$client->tts()->tts($payload)`.

| Setter                                      | Description                                                                                                |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `setText(...)`                              | Text to synthesize. For MARS Instruct, you can include inline emotion or pacing tags in the text.          |
| `setLanguage(...)`                          | Locale such as `CreateStreamTTSRequestPayload::LANGUAGE_EN_US`.                                            |
| `setVoiceId(...)`                           | Voice profile ID from `listVoicesListVoicesGet()`.                                                         |
| `setSpeechModel(...)`                       | Model to use, such as `SPEECH_MODEL_MARS_FLASH`, `SPEECH_MODEL_MARS_PRO`, or `SPEECH_MODEL_MARS_INSTRUCT`. |
| `setUserInstructions(...)`                  | Adds style, tone, pronunciation, or delivery guidance for the request. Available only with MARS Instruct.  |
| `setOutputConfiguration(...)`               | Output settings such as audio format.                                                                      |
| `setVoiceSettings(...)`                     | Voice behavior controls such as speaking rate, reference enhancement, or accent preservation.              |
| `setInferenceOptions(...)`                  | Advanced generation controls for supported models.                                                         |
| `setEnhanceNamedEntitiesPronunciation(...)` | Improves pronunciation for names and other named entities when supported.                                  |

***

## Voices

`VoiceCloningApi` lists voices and creates custom voices. Instantiate it with `$httpClient` and `$config` from [Authentication](#authentication).

### List voices

Call `listVoicesListVoicesGet()` to retrieve voices on your account. The `id` field is the `voice_id` you pass to TTS.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\Api\VoiceCloningApi;
use Camb\Ai\Configuration;
use GuzzleHttp\Client;

$config = Configuration::getDefaultConfiguration()->setApiKey('x-api-key', getenv('CAMB_API_KEY'));
$httpClient = new Client(['timeout' => 300.0]);

$voiceApi = new VoiceCloningApi($httpClient, $config);
$voices = $voiceApi->listVoicesListVoicesGet();

foreach ($voices as $voice) {
    echo 'ID: ' . $voice->getId() . ', Name: ' . $voice->getVoiceName() . PHP_EOL;
}
```

### Create a custom voice

Upload a reference clip as [`SplFileObject`](https://www.php.net/manual/en/class.splfileobject.php). Set `Gender` using the numeric enum values exposed on the model class (for example `Gender::NUMBER_1`). Optional `enhance_audio` helps noisy recordings.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\Api\VoiceCloningApi;
use Camb\Ai\Configuration;
use Camb\Ai\Model\Gender;
use Camb\Ai\Model\Languages;
use GuzzleHttp\Client;

$config = Configuration::getDefaultConfiguration()->setApiKey('x-api-key', getenv('CAMB_API_KEY'));
$httpClient = new Client(['timeout' => 300.0]);

$voiceApi = new VoiceCloningApi($httpClient, $config);
$file = new \SplFileObject(__DIR__ . '/reference.wav');

$result = $voiceApi->createCustomVoiceCreateCustomVoicePost(
    'My Custom Voice',
    Gender::NUMBER_1,
    $file,
    null,
    'Warm and conversational.',
    null,
    30,
    Languages::EN_US,
    true
);
```

***

## Language Support

Camb.ai supports up to 312 languages depending on the MARS model — see [Language Support](/language-support) for the full per-model locale list. Prefer `Languages` enum values on payloads so editors catch invalid codes.

```php theme={null}
use Camb\Ai\Model\Languages;

$payload->setLanguage(Languages::EN_US);
$payload->setLanguage(Languages::FR_FR);
```

To fetch supported languages at runtime, use `LanguagesApi` with the same configuration pattern as voice cloning.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\Api\LanguagesApi;
use Camb\Ai\Configuration;
use GuzzleHttp\Client;

$config = Configuration::getDefaultConfiguration()->setApiKey('x-api-key', getenv('CAMB_API_KEY'));
$httpClient = new Client(['timeout' => 300.0]);

$langApi = new LanguagesApi($httpClient, $config);

$sourceLanguages = $langApi->getSourceLanguagesSourceLanguagesGet();
$targetLanguages = $langApi->getTargetLanguagesTargetLanguagesGet();
```

Per-model language lists are documented under [MARS Models](/models).

***

## Dubbing

End-to-end dubbing takes a public video URL, translates the track, and synthesizes speech that matches the speaker. Submit the job, poll status, then read URLs from the dubbing result.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\CambAIClient;
use Camb\Ai\Model\EndToEndDubbingRequestPayload;
use Camb\Ai\Model\Languages;
use Camb\Ai\Model\TaskStatus;

$client = new CambAIClient(getenv('CAMB_API_KEY'));

$payload = new EndToEndDubbingRequestPayload();
$payload->setVideoUrl('https://example.com/video.mp4');
$payload->setSourceLanguage(Languages::EN_US);
$payload->setTargetLanguages([Languages::HI_IN, Languages::FR_FR]);

$queued = $client->dub->endToEndDubbingDubPost($payload);
$taskId = $queued->getTaskId();

while (true) {
    sleep(5);
    $statusResult = $client->dub->getEndToEndDubbingStatusDubTaskIdGet($taskId);
    $status = $statusResult->getStatus();

    if ($status === TaskStatus::SUCCESS) {
        $runId = $statusResult->getRunId();
        $dubResult = $client->dub->getDubbedRunInfoDubResultRunIdGet($runId);
        $videoUrl = $dubResult->getDubbingResult()->getVideoUrl();
        break;
    }

    if ($status === TaskStatus::ERROR) {
        break;
    }
}
```

### Get transcript

After a successful dub, fetch the transcript for a target language using `getDubbedRunTranscriptTranscriptRunIdLanguageGet()`.

```php theme={null}
$transcript = $client->dub->getDubbedRunTranscriptTranscriptRunIdLanguageGet(
    $runId,
    Languages::HI_IN
);
```

***

## Translation

Translation accepts an ordered list of strings and returns translations in the same order. Submit a job, poll `getTranslationTaskStatusTranslateTaskIdGet()`, then load results with `getTranslationResultTranslationResultRunIdGet()`.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\Api\TranslationApi;
use Camb\Ai\Configuration;
use Camb\Ai\Model\CreateTranslationRequestPayload;
use Camb\Ai\Model\Languages;
use Camb\Ai\Model\TaskStatus;
use GuzzleHttp\Client;

$config = Configuration::getDefaultConfiguration()->setApiKey('x-api-key', getenv('CAMB_API_KEY'));
$httpClient = new Client(['timeout' => 300.0]);

$translationApi = new TranslationApi($httpClient, $config);

$payload = new CreateTranslationRequestPayload();
$payload->setTexts(['Hello, how are you?', 'Welcome to Camb.ai.']);
$payload->setSourceLanguage(Languages::EN_US);
$payload->setTargetLanguage(Languages::FR_FR);

$submit = $translationApi->createTranslationTranslatePost($payload);
$taskId = $submit->task_id;

while (true) {
    sleep(2);
    $status = $translationApi->getTranslationTaskStatusTranslateTaskIdGet($taskId);
    if ($status->getStatus() === TaskStatus::SUCCESS) {
        $runId = $status->getRunId();
        $result = $translationApi->getTranslationResultTranslationResultRunIdGet($runId);
        foreach ($result->getTexts() as $text) {
            echo $text . PHP_EOL;
        }
        break;
    }
    if ($status->getStatus() === TaskStatus::ERROR) {
        break;
    }
}
```

<Note>
  `createTranslationTranslatePost()` processes every string in `texts` in a single job and preserves ordering in the result. The OpenAPI client types this response as `mixed`, so the JSON-decoded object exposes `task_id` as a property rather than `getTaskId()`.
</Note>

***

## Transcription

Send audio or video by URL or as an uploaded file. Poll `getTranscriptionTaskStatusTranscribeTaskIdGet()`, then call `getTranscriptionResultTranscriptionResultRunIdGet()` with optional word-level timestamps.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\Api\TranscriptionApi;
use Camb\Ai\Configuration;
use Camb\Ai\Model\Languages;
use Camb\Ai\Model\TaskStatus;
use GuzzleHttp\Client;

$config = Configuration::getDefaultConfiguration()->setApiKey('x-api-key', getenv('CAMB_API_KEY'));
$httpClient = new Client(['timeout' => 300.0]);

$transcriptionApi = new TranscriptionApi($httpClient, $config);

$queued = $transcriptionApi->createTranscriptionTranscribePost(
    Languages::EN_US,
    null,
    null,
    'https://example.com/audio.mp3'
);
$taskId = $queued->getTaskId();

while (true) {
    sleep(3);
    $status = $transcriptionApi->getTranscriptionTaskStatusTranscribeTaskIdGet($taskId);
    if ($status->getStatus() === TaskStatus::SUCCESS) {
        $runId = $status->getRunId();
        $result = $transcriptionApi->getTranscriptionResultTranscriptionResultRunIdGet($runId, true);
        break;
    }
    if ($status->getStatus() === TaskStatus::ERROR) {
        break;
    }
}
```

For a local file, pass [`SplFileObject`](https://www.php.net/manual/en/class.splfileobject.php) as the `file` argument (fifth parameter).

```php theme={null}
$file = new \SplFileObject(__DIR__ . '/audio.mp3');
$queued = $transcriptionApi->createTranscriptionTranscribePost(
    Languages::EN_US,
    null,
    null,
    null,
    $file
);
```

***

## Audio Separation

Separation splits a mixed recording into stems. Upload media with `createAudioSeparationAudioSeparationPost()`, poll `getAudioSeparationStatusAudioSeparationTaskIdGet()`, then read `getAudioSeparationRunInfoAudioSeparationResultRunIdGet()`.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\Api\AudioSeparationApi;
use Camb\Ai\Configuration;
use Camb\Ai\Model\TaskStatus;
use GuzzleHttp\Client;

$config = Configuration::getDefaultConfiguration()->setApiKey('x-api-key', getenv('CAMB_API_KEY'));
$httpClient = new Client(['timeout' => 300.0]);

$audioSeparationApi = new AudioSeparationApi($httpClient, $config);
$media = new \SplFileObject(__DIR__ . '/track.mp3');

$queued = $audioSeparationApi->createAudioSeparationAudioSeparationPost(null, null, $media);
$taskId = $queued->getTaskId();

while (true) {
    sleep(3);
    $status = $audioSeparationApi->getAudioSeparationStatusAudioSeparationTaskIdGet($taskId);
    if ($status->getStatus() === TaskStatus::SUCCESS) {
        $runId = $status->getRunId();
        $result = $audioSeparationApi->getAudioSeparationRunInfoAudioSeparationResultRunIdGet($runId);
        break;
    }
    if ($status->getStatus() === TaskStatus::ERROR) {
        break;
    }
}
```

***

## Text-to-Voice

Text-to-Voice generates a new voice from a written description. `CambAIClient` exposes `textToVoice`. Poll until `TaskStatus::SUCCESS`, then inspect previews from `getTextToVoiceResultTextToVoiceResultRunIdGet()`.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\CambAIClient;
use Camb\Ai\Model\CreateTextToVoiceRequestPayload;
use Camb\Ai\Model\TaskStatus;

$client = new CambAIClient(getenv('CAMB_API_KEY'));

$payload = new CreateTextToVoiceRequestPayload();
$payload->setText('A confident narrator introducing a documentary.');
$payload->setVoiceDescription('Deep, measured baritone with a slight gravel. Calm and authoritative.');

$queued = $client->textToVoice->createTextToVoiceTextToVoicePost($payload);
$taskId = $queued->getTaskId();

while (true) {
    sleep(2);
    $status = $client->textToVoice->getTextToVoiceStatusTextToVoiceTaskIdGet($taskId);
    if ($status->getStatus() === TaskStatus::SUCCESS) {
        $runId = $status->getRunId();
        $voiceResult = $client->textToVoice->getTextToVoiceResultTextToVoiceResultRunIdGet($runId);
        break;
    }
    if ($status->getStatus() === TaskStatus::ERROR) {
        break;
    }
}
```

<Note>
  Preview URLs let you audition variations. Use the voice ID you choose with streaming TTS when you move to production.
</Note>

***

## Text-to-Audio

Text-to-Audio turns a descriptive prompt into sound effects or ambience. Use `textToAudio` on the client, poll status, then stream the WAV result to disk.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\CambAIClient;
use Camb\Ai\Model\CreateTextToAudioRequestPayload;
use Camb\Ai\Model\TaskStatus;
use Camb\Ai\Model\TextToAudioType;

$client = new CambAIClient(getenv('CAMB_API_KEY'));

$payload = new CreateTextToAudioRequestPayload();
$payload->setPrompt('Heavy rain on a tin roof at night with distant thunder.');
$payload->setDuration(15.0);
$payload->setAudioType(TextToAudioType::SOUND);

$queued = $client->textToAudio->createTextToAudioTextToSoundPost($payload);
$taskId = $queued->getTaskId();

while (true) {
    sleep(2);
    $status = $client->textToAudio->getTextToAudioStatusTextToSoundTaskIdGet($taskId);
    if ($status->getStatus() === TaskStatus::SUCCESS) {
        $runId = $status->getRunId();
        $audioStream = $client->textToAudio->getTextToAudioResultTextToSoundResultRunIdGet($runId);
        $out = fopen('soundscape.wav', 'w');
        while (!$audioStream->eof()) {
            fwrite($out, $audioStream->fread(8192));
        }
        fclose($out);
        break;
    }
    if ($status->getStatus() === TaskStatus::ERROR) {
        break;
    }
}
```

***

## Stories

The Stories API ingests a document, builds narration, and returns audio output. Use `StoryApi` with `createStoryStoryPost()`, poll `getStoryStatusStoryTaskIdGet()`, then load `getStoryRunInfoStoryResultRunIdGet()`.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\Api\StoryApi;
use Camb\Ai\Configuration;
use Camb\Ai\Model\Languages;
use Camb\Ai\Model\TaskStatus;
use GuzzleHttp\Client;

$config = Configuration::getDefaultConfiguration()->setApiKey('x-api-key', getenv('CAMB_API_KEY'));
$httpClient = new Client(['timeout' => 300.0]);

$storyApi = new StoryApi($httpClient, $config);
$file = new \SplFileObject(__DIR__ . '/story.pdf');

$queued = $storyApi->createStoryStoryPost($file, Languages::EN_US, null, 'My Story');
$taskId = $queued->getTaskId();

while (true) {
    sleep(5);
    $status = $storyApi->getStoryStatusStoryTaskIdGet($taskId);
    if ($status->getStatus() === TaskStatus::SUCCESS) {
        $runId = $status->getRunId();
        $info = $storyApi->getStoryRunInfoStoryResultRunIdGet($runId);
        break;
    }
    if ($status->getStatus() === TaskStatus::ERROR) {
        break;
    }
}
```

***

## Translated TTS

Translated TTS runs translation and synthesis together. Use `TranslatedTTSApi`, poll `getTranslatedTtsTaskStatusTranslatedTtsTaskIdGet()`, and inspect the orchestrator payload when the job succeeds.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\Api\TranslatedTTSApi;
use Camb\Ai\Configuration;
use Camb\Ai\Model\CreateTranslatedTTSRequestPayload;
use Camb\Ai\Model\Languages;
use Camb\Ai\Model\TaskStatus;
use GuzzleHttp\Client;

$config = Configuration::getDefaultConfiguration()->setApiKey('x-api-key', getenv('CAMB_API_KEY'));
$httpClient = new Client(['timeout' => 300.0]);

$ttsApi = new TranslatedTTSApi($httpClient, $config);

$payload = new CreateTranslatedTTSRequestPayload();
$payload->setText('Good morning, welcome to our service.');
$payload->setVoiceId(147320);
$payload->setSourceLanguage(Languages::EN_US);
$payload->setTargetLanguage(Languages::HI_IN);

$queued = $ttsApi->createTranslatedTtsTranslatedTtsPost($payload);
$taskId = $queued->getTaskId();

while (true) {
    sleep(3);
    $status = $ttsApi->getTranslatedTtsTaskStatusTranslatedTtsTaskIdGet($taskId);
    if ($status->getStatus() === TaskStatus::SUCCESS) {
        break;
    }
    if ($status->getStatus() === TaskStatus::ERROR) {
        break;
    }
}
```

***

## Dictionaries

Dictionaries store term mappings for dubbing and translation workflows.

### List dictionaries

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\Api\DictionariesApi;
use Camb\Ai\Configuration;
use GuzzleHttp\Client;

$config = Configuration::getDefaultConfiguration()->setApiKey('x-api-key', getenv('CAMB_API_KEY'));
$httpClient = new Client(['timeout' => 300.0]);

$dictApi = new DictionariesApi($httpClient, $config);
$dictionaries = $dictApi->getDictionariesDictionariesGet();

foreach ($dictionaries as $dictionary) {
    echo $dictionary->getId() . ': ' . $dictionary->getName() . PHP_EOL;
}
```

### Create from file

```php theme={null}
$dictionaryFile = new \SplFileObject(__DIR__ . '/terms.csv');
$dictApi->createDictionaryFromFileDictionariesCreateFromFilePost(
    $dictionaryFile,
    'Product Terms',
    null,
    'Brand-specific terminology for our product line.'
);
```

### Manage terms

`TermTranslationInput` pairs localized text with a `Languages` value. Build `AddDictionaryTermPayload` and call `addTermToDictionaryDictionariesDictionaryIdAddTermPost()`.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Camb\Ai\Api\DictionariesApi;
use Camb\Ai\Configuration;
use Camb\Ai\Model\AddDictionaryTermPayload;
use Camb\Ai\Model\Languages;
use Camb\Ai\Model\TermTranslationInput;
use GuzzleHttp\Client;

$config = Configuration::getDefaultConfiguration()->setApiKey('x-api-key', getenv('CAMB_API_KEY'));
$httpClient = new Client(['timeout' => 300.0]);

$dictApi = new DictionariesApi($httpClient, $config);

$termInput = new TermTranslationInput();
$termInput->setTranslation('कैम्ब.एआई');
$termInput->setLanguage(Languages::HI_IN);

$payload = new AddDictionaryTermPayload();
$payload->setTranslations([$termInput]);

$dictionaryId = 123;
$dictApi->addTermToDictionaryDictionariesDictionaryIdAddTermPost($dictionaryId, $payload);

$dictApi->deleteDictionaryTermDictionariesTermDictionaryIdTermIdDelete($dictionaryId, 'term_456');

$dictApi->deleteDictionaryDictionariesDictionaryIdDelete($dictionaryId);
```

***

## Custom Provider

If you self-host MARS on Baseten or another backend, implement `TtsProviderInterface` and register it with `setTtsProvider()`. See [Custom Cloud Providers](/custom-cloud-providers) for deployment details.

### Baseten

Point the provider at your deployment URL and supply credentials from the environment.

```php theme={null}
<?php
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/BasetenTtsProvider.php';

use Camb\Ai\CambAIClient;

$client = new CambAIClient(getenv('CAMB_API_KEY'));

$basetenProvider = new BasetenTtsProvider(
    getenv('BASETEN_API_KEY'),
    getenv('BASETEN_MODEL_URL')
);
$client->setTtsProvider($basetenProvider);
```

Copy [`BasetenTtsProvider.php`](https://github.com/Camb-ai/cambai-php-sdk/blob/master/examples/BasetenTtsProvider.php) from the SDK `examples` folder into your project (next to the script or on your include path) so the `require_once` resolves.

***

## 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-php-sdk](https://github.com/Camb-ai/cambai-php-sdk)
* [SDK Examples](https://github.com/Camb-ai/cambai-php-sdk/tree/master/examples)
* [API Reference](/api-reference/endpoint/create-tts-stream)
