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

# Go SDK

> Install and use the Go SDK with context-based cancellation and streaming audio

The official Go SDK for Camb.ai provides convenient access to text-to-speech, dubbing, translation, transcription, voice cloning, and audio generation. It uses functional options for configuration and returns `io.Reader` streams for audio output.

## Installation

```bash theme={null}
go get github.com/camb-ai/cambai-go-sdk
```

The module currently declares **Go 1.13** as its minimum version in `go.mod`. Newer Go releases work with the module; use the version your team standardizes on for builds and CI.

***

## Authentication

Get your API key from [Camb.ai Studio](https://studio.camb.ai) and pass it with `option.WithAPIKey`. Reading `CAMB_API_KEY` from the environment keeps secrets out of source control.

```go theme={null}
import (
	"github.com/camb-ai/cambai-go-sdk/client"
	"github.com/camb-ai/cambai-go-sdk/option"
)

c := client.NewClient(option.WithAPIKey(os.Getenv("CAMB_API_KEY")))
```

<Tip>
  For local development, load `CAMB_API_KEY` from a `.env` file with a small helper or your process manager so the value never lands in version control.
</Tip>

***

## Imports

The root module is `package cambai`, so the default import name is `cambai` when you import `github.com/camb-ai/cambai-go-sdk`. You may also use an explicit alias (`import cambai "github.com/camb-ai/cambai-go-sdk"`) for clarity in larger codebases. The snippets below assume the default name `cambai`.

***

## Quick Start

`TextToSpeech.Tts` returns an `io.Reader` streaming audio. Copy it to a file (or another writer) with `io.Copy`.

```go theme={null}
package main

import (
	"context"
	"io"
	"os"

	"github.com/camb-ai/cambai-go-sdk"
	"github.com/camb-ai/cambai-go-sdk/client"
	"github.com/camb-ai/cambai-go-sdk/option"
)

func main() {
	c := client.NewClient(option.WithAPIKey(os.Getenv("CAMB_API_KEY")))

	stream, err := c.TextToSpeech.Tts(
		context.Background(),
		&cambai.CreateStreamTtsRequestPayload{
			Text:        "Hello from the Camb.ai Go SDK.",
			VoiceID:     147320,
			Language:    cambai.CreateStreamTtsRequestPayloadLanguageEnUs,
			SpeechModel: cambai.CreateStreamTtsRequestPayloadSpeechModelMarsFlash.Ptr(),
			OutputConfiguration: &cambai.StreamTtsOutputConfiguration{
				Format: cambai.OutputFormatWav.Ptr(),
			},
		},
	)
	if err != nil {
		panic(err)
	}

	out, err := os.Create("output.wav")
	if err != nil {
		panic(err)
	}
	defer out.Close()

	if _, err := io.Copy(out, stream); err != nil {
		panic(err)
	}
}
```

The snippets in the sections below assume `c` and `ctx` are initialized as shown above.

***

## Models

Camb.ai offers MARS models tuned for different quality and latency needs. Set `SpeechModel` on `CreateStreamTtsRequestPayload` using the generated constants (for example `CreateStreamTtsRequestPayloadSpeechModelMarsFlash`). 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                    |

<Tabs>
  <Tab title="MARS Flash">
    ```go theme={null}
    stream, err := c.TextToSpeech.Tts(ctx, &cambai.CreateStreamTtsRequestPayload{
    	Text:        "Hey, I can respond much faster.",
    	Language:    cambai.CreateStreamTtsRequestPayloadLanguageEnUs,
    	VoiceID:     147320,
    	SpeechModel: cambai.CreateStreamTtsRequestPayloadSpeechModelMarsFlash.Ptr(),
    	OutputConfiguration: &cambai.StreamTtsOutputConfiguration{
    		Format: cambai.OutputFormatWav.Ptr(),
    	},
    })
    if err != nil {
    	panic(err)
    }
    _ = stream
    ```

    **Best for:** Voice agents and real-time applications.

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

  <Tab title="MARS Pro">
    ```go theme={null}
    stream, err := c.TextToSpeech.Tts(ctx, &cambai.CreateStreamTtsRequestPayload{
    	Text:        "High-fidelity audio for production use.",
    	Language:    cambai.CreateStreamTtsRequestPayloadLanguageEnUs,
    	VoiceID:     147320,
    	SpeechModel: cambai.CreateStreamTtsRequestPayloadSpeechModelMarsPro.Ptr(),
    	OutputConfiguration: &cambai.StreamTtsOutputConfiguration{
    		Format: cambai.OutputFormatWav.Ptr(),
    	},
    })
    if err != nil {
    	panic(err)
    }
    _ = stream
    ```

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

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

  <Tab title="MARS Instruct">
    ```go theme={null}
    stream, err := c.TextToSpeech.Tts(ctx, &cambai.CreateStreamTtsRequestPayload{
    	Text:             "Great to meet you!",
    	Language:         cambai.CreateStreamTtsRequestPayloadLanguageEnUs,
    	VoiceID:          147320,
    	SpeechModel:      cambai.CreateStreamTtsRequestPayloadSpeechModelMarsInstruct.Ptr(),
    	UserInstructions: cambai.String("Speak in a warm, friendly tone"),
    	OutputConfiguration: &cambai.StreamTtsOutputConfiguration{
    		Format: cambai.OutputFormatWav.Ptr(),
    	},
    })
    if err != nil {
    	panic(err)
    }
    _ = stream
    ```

    **Best for:** Fine-grained control over tone and style.

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

The `ctx` and `c` variables in the model tabs match the authenticated client pattern from the Quick Start and Authentication sections.

***

## TTS Options

`c.TextToSpeech.Tts(...)` accepts `CreateStreamTtsRequestPayload` with the core request fields plus optional controls for model behavior and output format.

| Field                               | Type                                   | Description                                                                                                                |
| ----------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `Text`                              | string                                 | Text to synthesize. For MARS Instruct, you can include inline emotion or pacing tags in the text.                          |
| `Language`                          | enum                                   | Locale such as `cambai.CreateStreamTtsRequestPayloadLanguageEnUs`.                                                         |
| `VoiceID`                           | integer                                | Voice profile ID from `VoiceCloning.ListVoices`.                                                                           |
| `SpeechModel`                       | pointer enum/string                    | Model to use, such as `MarsFlash`, `MarsPro`, or `MarsInstruct`.                                                           |
| `UserInstructions`                  | string pointer                         | Adds style, tone, pronunciation, or delivery guidance for the request. Available only when `SpeechModel` is MARS Instruct. |
| `OutputConfiguration`               | `*cambai.StreamTtsOutputConfiguration` | Output settings such as audio format.                                                                                      |
| `VoiceSettings`                     | object                                 | Voice behavior controls such as speaking rate, reference enhancement, or accent preservation.                              |
| `InferenceOptions`                  | object                                 | Advanced generation controls for supported models.                                                                         |
| `EnhanceNamedEntitiesPronunciation` | boolean pointer                        | Improves pronunciation for names and other named entities when supported.                                                  |

***

## Voices

`VoiceCloning.ListVoices` returns the voices available to your account.

```go theme={null}
voices, err := c.VoiceCloning.ListVoices(ctx, &cambai.ListVoicesListVoicesGetRequest{})
if err != nil {
	panic(err)
}
for _, item := range voices {
	// item is a discriminated union; visit to get the typed Voice value.
	if v := item.Voice; v != nil {
		fmt.Println(v.ID, v.VoiceName)
	}
}
```

`VoiceCloning.CreateCustomVoice` accepts `BodyCreateCustomVoiceCreateCustomVoicePost` (voice name, `Gender`, optional description, and other metadata). Map `Gender` to the integer values described in the [Create custom voice](https://docs.camb.ai/api-reference/endpoint/create-custom-voice) reference. The generated request type in this module does not include a sample audio field; if your workflow requires uploading a clip, use the REST multipart contract or confirm the latest SDK types match your needs.

***

## Language support

TTS requests use typed language constants on `CreateStreamTtsRequestPayload` (for example `CreateStreamTtsRequestPayloadLanguageEnUs`). See [Language Support](/language-support) for the full per-model locale list. Dubbing, translation, transcription, and related payloads use the shared `cambai.Languages` type (for example `cambai.LanguagesEnUs`).

`Languages.GetSourceLanguages` and `Languages.GetTargetLanguages` return the language lists exposed by the API for products that distinguish source and target sets.

```go theme={null}
source, err := c.Languages.GetSourceLanguages(ctx, &cambai.GetSourceLanguagesSourceLanguagesGetRequest{})
if err != nil {
	panic(err)
}
targets, err := c.Languages.GetTargetLanguages(ctx, &cambai.GetTargetLanguagesTargetLanguagesGetRequest{})
if err != nil {
	panic(err)
}
fmt.Println(len(source), len(targets))
```

Per-model language coverage is summarized on the [Models](/models) page.

***

## Dubbing

Dubbing takes a public video URL, translates the audio into your target language, and synthesizes speech. The flow is asynchronous: submit with `Dub.EndToEndDubbing`, poll with `Dub.GetEndToEndDubbingStatus`, then read the output with `Dub.GetDubbedRunInfo`. Go uses **`EndToEndDubbing`** (same naming as the TypeScript SDK, unlike Python `create_dub`).

```go theme={null}
resp, err := c.Dub.EndToEndDubbing(ctx, &cambai.EndToEndDubbingRequestPayload{
	VideoURL:       "https://example.com/video.mp4",
	SourceLanguage: cambai.LanguagesEnUs,
	TargetLanguage: cambai.LanguagesFrFr.Ptr(),
})
if err != nil {
	panic(err)
}
taskID := *resp.TaskID
```

Poll `Dub.GetEndToEndDubbingStatus` with that task id until `Status` is `cambai.TaskStatusSuccess` or `cambai.TaskStatusError`, then call `Dub.GetDubbedRunInfo` with the `RunID` from the status object. See `examples/dubbing` in the [cambai-go-sdk](https://github.com/camb-ai/cambai-go-sdk) repository for a full loop.

For multiple targets in one job, set `TargetLanguages` instead of `TargetLanguage`.

### Transcript

After a successful run, `Dub.GetDubbedRunTranscript` returns transcript data for a target language.

```go theme={null}
// runID comes from GetEndToEndDubbingStatus after the job succeeds.
txt := cambai.TranscriptFileFormatTxt
transcript, err := c.Dub.GetDubbedRunTranscript(
	ctx,
	&runID,
	cambai.LanguagesFrFr,
	&cambai.GetDubbedRunTranscriptTranscriptRunIDLanguageGetRequest{
		FormatType: txt.Ptr(),
	},
)
if err != nil {
	panic(err)
}
fmt.Println(transcript)
```

***

## Translation

Submit strings with `Translation.CreateTranslation`, poll `GetTranslationTaskStatus`, then fetch strings with `GetTranslationResult`. `CreateTranslation` is typed as `interface{}` in this client; round-trip through JSON is a reliable way to read `task_id` into `OrchestratorPipelineCallResult`.

```go theme={null}
raw, err := c.Translation.CreateTranslation(ctx, &cambai.CreateTranslationRequestPayload{
	Texts:          []string{"Hello, how are you?", "Welcome to Camb.ai."},
	SourceLanguage: cambai.LanguagesEnUs,
	TargetLanguage: cambai.LanguagesFrFr,
})
if err != nil {
	panic(err)
}

// CreateTranslation returns interface{}; round-trip through JSON to read task_id.
b, _ := json.Marshal(raw)
var started cambai.OrchestratorPipelineCallResult
json.Unmarshal(b, &started)
taskID := *started.TaskID

for {
	time.Sleep(2 * time.Second)
	status, err := c.Translation.GetTranslationTaskStatus(
		ctx, taskID, &cambai.GetTranslationTaskStatusTranslateTaskIDGetRequest{},
	)
	if err != nil {
		panic(err)
	}
	if status.Status == cambai.TaskStatusSuccess && status.RunID != nil {
		result, err := c.Translation.GetTranslationResult(
			ctx, status.RunID, &cambai.GetTranslationResultTranslationResultRunIDGetRequest{},
		)
		if err != nil {
			panic(err)
		}
		fmt.Println(result.Texts)
		return
	}
	if status.Status == cambai.TaskStatusError {
		panic("translation failed")
	}
}
```

***

## Transcription

Pass a remote URL with `MediaURL` on `BodyCreateTranscriptionTranscribePost`. Poll `GetTranscriptionTaskStatus`, then call `GetTranscriptionResult` with optional word-level timestamps.

```go theme={null}
resp, err := c.Transcription.CreateTranscription(ctx, &cambai.BodyCreateTranscriptionTranscribePost{
	Language: cambai.LanguagesEnUs,
	MediaURL: cambai.String("https://example.com/audio.mp3"),
})
if err != nil {
	panic(err)
}
taskID := *resp.TaskID

for {
	time.Sleep(3 * time.Second)
	status, err := c.Transcription.GetTranscriptionTaskStatus(
		ctx, taskID, &cambai.GetTranscriptionTaskStatusTranscribeTaskIDGetRequest{},
	)
	if err != nil {
		panic(err)
	}
	if status.Status == cambai.TaskStatusSuccess && status.RunID != nil {
		wl := true
		result, err := c.Transcription.GetTranscriptionResult(
			ctx, status.RunID,
			&cambai.GetTranscriptionResultTranscriptionResultRunIDGetRequest{WordLevelTimestamps: &wl},
		)
		if err != nil {
			panic(err)
		}
		fmt.Println(result)
		return
	}
	if status.Status == cambai.TaskStatusError {
		panic("transcription failed")
	}
}
```

The generated transcription request type in this module exposes `MediaURL` (and deprecated `AudioURL`). If you need to upload a local file from Go, confirm the latest SDK supports the multipart fields your server expects, or use the REST API directly.

***

## Audio separation

The client exposes `AudioSeparation.CreateAudioSeparation`, `GetAudioSeparationStatus`, `GetAudioSeparationRunInfo`, and batch helpers. The generated `BodyCreateAudioSeparationAudioSeparationPost` type in this repository currently carries metadata fields only. For production uploads, validate the request type against [Audio separation](https://docs.camb.ai/api-reference) in your SDK version or call the REST endpoints until the generator includes the media parts you rely on.

***

## Text-to-voice

Text-to-voice creates candidate voices from a description. Submit with `TextToVoice.CreateTextToVoice`, poll `GetTextToVoiceStatus`, then read previews from `GetTextToVoiceResult`.

```go theme={null}
resp, err := c.TextToVoice.CreateTextToVoice(ctx, &cambai.CreateTextToVoiceRequestPayload{
	Text:             "A confident narrator introducing a documentary.",
	VoiceDescription: "Deep, measured baritone with a slight gravel. Calm and authoritative.",
})
if err != nil {
	panic(err)
}
taskID := *resp.TaskID

for {
	time.Sleep(2 * time.Second)
	status, err := c.TextToVoice.GetTextToVoiceStatus(
		ctx, taskID, &cambai.GetTextToVoiceStatusTextToVoiceTaskIDGetRequest{},
	)
	if err != nil {
		panic(err)
	}
	if status.Status == cambai.TaskStatusSuccess && status.RunID != nil {
		runID := *status.RunID
		out, err := c.TextToVoice.GetTextToVoiceResult(
			ctx, &runID, &cambai.GetTextToVoiceResultTextToVoiceResultRunIDGetRequest{},
		)
		if err != nil {
			panic(err)
		}
		fmt.Println(out.Previews)
		return
	}
	if status.Status == cambai.TaskStatusError {
		panic("text-to-voice failed")
	}
}
```

The `GetTextToVoiceResultOut` value includes `Previews` (URLs) you can audition before choosing a `voice_id` for TTS.

***

## Text-to-audio

Text-to-audio generates sound from a prompt. The pattern matches other async jobs: `CreateTextToAudio`, `GetTextToAudioStatus`, then `GetTextToAudioResult` as an `io.Reader`.

```go theme={null}
resp, err := c.TextToAudio.CreateTextToAudio(ctx, &cambai.CreateTextToAudioRequestPayload{
	Prompt:    "Heavy rain on a tin roof at night with distant thunder.",
	Duration:  cambai.Float64(15),
	AudioType: cambai.TextToAudioTypeSound.Ptr(),
})
if err != nil {
	panic(err)
}
taskID := *resp.TaskID

for {
	time.Sleep(3 * time.Second)
	status, err := c.TextToAudio.GetTextToAudioStatus(
		ctx, taskID, &cambai.GetTextToAudioStatusTextToSoundTaskIDGetRequest{},
	)
	if err != nil {
		panic(err)
	}
	if status.Status == cambai.TaskStatusSuccess && status.RunID != nil {
		runID := *status.RunID
		stream, err := c.TextToAudio.GetTextToAudioResult(
			ctx, &runID, &cambai.GetTextToAudioResultTextToSoundResultRunIDGetRequest{},
		)
		if err != nil {
			panic(err)
		}
		out, _ := os.Create("soundscape.wav")
		defer out.Close()
		io.Copy(out, stream)
		return
	}
	if status.Status == cambai.TaskStatusError {
		panic("text-to-audio failed")
	}
}
```

***

## Stories

`Story.CreateStory` returns `*CreateStoryStoryPostResponse`, a discriminated union over `OrchestratorPipelineCallResult` (async `task_id`) and `GetSetupStoryResultResponse` (immediate setup payload). Use `Accept` with `CreateStoryStoryPostResponseVisitor` to read whichever branch the API returned, then poll `GetStoryStatus` when you received a task id.

The generated `BodyCreateStoryStoryPost` struct lists the fields this client sends. If you rely on uploading a source document, compare that struct to the REST story endpoint for your SDK revision.

```go theme={null}
// storyKickoff implements CreateStoryStoryPostResponseVisitor to extract either
// a task_id (async) or an immediate run_id (sync setup response).
type storyKickoff struct {
	taskID string
	runID  int
	mode   string
}

func (v *storyKickoff) VisitOrchestratorPipelineCallResult(r *cambai.OrchestratorPipelineCallResult) error {
	v.taskID = *r.TaskID
	v.mode = "async"
	return nil
}

func (v *storyKickoff) VisitGetSetupStoryResultResponse(r *cambai.GetSetupStoryResultResponse) error {
	v.runID = r.RunID
	v.mode = "sync"
	return nil
}

created, err := c.Story.CreateStory(ctx, &cambai.BodyCreateStoryStoryPost{
	SourceLanguage: cambai.LanguagesEnUs,
	Title:          cambai.String("My story"),
})
if err != nil {
	panic(err)
}

var kick storyKickoff
if err := created.Accept(&kick); err != nil {
	panic(err)
}

if kick.mode == "async" {
	for {
		time.Sleep(5 * time.Second)
		status, err := c.Story.GetStoryStatus(ctx, kick.taskID, &cambai.GetStoryStatusStoryTaskIDGetRequest{})
		if err != nil {
			panic(err)
		}
		if status.Status == cambai.TaskStatusSuccess && status.RunID != nil {
			runID := *status.RunID
			info, err := c.Story.GetStoryRunInfo(ctx, &runID, &cambai.GetStoryRunInfoStoryResultRunIDGetRequest{})
			if err != nil {
				panic(err)
			}
			fmt.Println(info)
			return
		}
		if status.Status == cambai.TaskStatusError {
			panic("story failed")
		}
	}
}
```

***

## Translated TTS

`TranslatedTts.CreateTranslatedTts` returns `CreateTranslatedTtsOut` with a string `TaskID`. Poll with `GetTranslatedTtsTaskStatus` until `Status` is success or error.

```go theme={null}
out, err := c.TranslatedTts.CreateTranslatedTts(ctx, &cambai.CreateTranslatedTtsRequestPayload{
	Text:           "Good morning, welcome to our service.",
	VoiceID:        147320,
	SourceLanguage: cambai.LanguagesEnUs,
	TargetLanguage: cambai.LanguagesHiIn,
})
if err != nil {
	panic(err)
}

for {
	time.Sleep(3 * time.Second)
	status, err := c.TranslatedTts.GetTranslatedTtsTaskStatus(
		ctx, out.TaskID, &cambai.GetTranslatedTtsTaskStatusTranslatedTtsTaskIDGetRequest{},
	)
	if err != nil {
		panic(err)
	}
	if status.Status == cambai.TaskStatusSuccess {
		fmt.Println(status)
		return
	}
	if status.Status == cambai.TaskStatusError {
		panic("translated tts failed")
	}
}
```

***

## Dictionaries

`Dictionaries.GetDictionaries` lists dictionaries. `AddTermToDictionary` accepts `TermTranslationInput` entries (`Translation` string plus `Language`). IDs are integers.

```go theme={null}
dicts, err := c.Dictionaries.GetDictionaries(ctx, &cambai.GetDictionariesDictionariesGetRequest{})
if err != nil {
	panic(err)
}
dictionaryID := dicts[0].ID

_, err = c.Dictionaries.AddTermToDictionary(ctx, dictionaryID, &cambai.AddDictionaryTermPayload{
	Translations: []*cambai.TermTranslationInput{
		{Translation: "कैम्ब.एआई", Language: cambai.LanguagesHiIn},
	},
})
if err != nil {
	panic(err)
}
```

`DeleteDictionaryTerm` and `DeleteDictionary` take integer IDs and optional `RunID` query parameters on their request structs. The `CreateDictionaryFromFile` helper in this module builds multipart metadata only; for CSV-based creation, prefer the REST contract in the API reference until the client matches it.

***

## Folders

`Folders.ListFolders` and `Folders.CreateFolder` organize runs in Studio-compatible projects. Pass the generated request structs from the `cambai` package for optional filters.

***

## Error handling

API failures surface as `error` values. Validation responses decode to `*cambai.UnprocessableEntityError`, which embeds `*core.APIError`. Use `errors.As` to inspect status codes and optional `Body` details.

```go theme={null}
package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"os"

	"github.com/camb-ai/cambai-go-sdk"
	"github.com/camb-ai/cambai-go-sdk/client"
	"github.com/camb-ai/cambai-go-sdk/core"
	"github.com/camb-ai/cambai-go-sdk/option"
)

func main() {
	c := client.NewClient(option.WithAPIKey(os.Getenv("CAMB_API_KEY")))
	ctx := context.Background()

	request := &cambai.CreateStreamTtsRequestPayload{
		Text:        "Hello world.",
		VoiceID:     147320,
		Language:    cambai.CreateStreamTtsRequestPayloadLanguageEnUs,
		SpeechModel: cambai.CreateStreamTtsRequestPayloadSpeechModelMarsFlash.Ptr(),
		OutputConfiguration: &cambai.StreamTtsOutputConfiguration{
			Format: cambai.OutputFormatWav.Ptr(),
		},
	}

	stream, err := c.TextToSpeech.Tts(ctx, request)
	if err != nil {
		var validation *cambai.UnprocessableEntityError
		if errors.As(err, &validation) {
			fmt.Println("validation:", validation.Body)
			os.Exit(1)
		}
		var apiErr *core.APIError
		if errors.As(err, &apiErr) {
			fmt.Println("status:", apiErr.StatusCode)
			os.Exit(1)
		}
		panic(err)
	}
	_, _ = io.Copy(io.Discard, stream)
}
```

Per-request tuning uses functional options from `github.com/camb-ai/cambai-go-sdk/option`, for example `option.WithMaxAttempts(3)`.

***

## Custom provider (Baseten)

The Go SDK does **not** mirror the TypeScript `ttsProvider` constructor flag on the main client. Instead, implement `provider.TtsProvider` from `github.com/camb-ai/cambai-go-sdk/provider` and call `Tts` yourself (or inject the implementation into your own stack). See the [baseten-provider example](https://github.com/camb-ai/cambai-go-sdk/blob/master/examples/baseten-provider/main.go) in the Go SDK repository for a complete `BasetenProvider` struct, HTTP wiring, and streaming output to disk.

```go theme={null}
package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"

	cambai "github.com/camb-ai/cambai-go-sdk"
	"github.com/camb-ai/cambai-go-sdk/provider"
)

// BasetenProvider implements provider.TtsProvider for the Baseten Mars8-Flash model.
// API reference: https://www.baseten.co/library/mars8-flash/
type BasetenProvider struct {
	APIKey            string
	URL               string
	ReferenceAudio    string
	ReferenceLanguage string
}

// CreateTts is a stub because Baseten does not support async TTS.
func (b *BasetenProvider) CreateTts(ctx context.Context, request *cambai.CreateTtsRequestPayload) (*cambai.CreateTtsOut, error) {
	return nil, fmt.Errorf("Baseten custom hosting provider does not support async CreateTts; use Tts (streaming) instead")
}

// Tts calls the Baseten Mars8-Flash endpoint and returns the audio as an io.Reader.
func (b *BasetenProvider) Tts(ctx context.Context, request *cambai.CreateStreamTtsRequestPayload) (io.Reader, error) {
	langStr := strings.ToLower(strings.ReplaceAll(string(request.Language), "_", "-"))

	payload := map[string]interface{}{
		"text":               request.Text,
		"language":           langStr,
		"output_duration":    nil,
		"reference_audio":    b.ReferenceAudio,
		"reference_language": b.ReferenceLanguage,
		"output_format":      "flac",
		"apply_ner_nlp":      false,
	}

	payloadBytes, err := json.Marshal(payload)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal payload: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, "POST", b.URL, bytes.NewBuffer(payloadBytes))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}
	req.Header.Set("Authorization", "Api-Key "+b.APIKey)
	req.Header.Set("Content-Type", "application/json")

	httpClient := &http.Client{}
	resp, err := httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("baseten error (%d): %s", resp.StatusCode, string(body))
	}

	var buf bytes.Buffer
	if _, err = io.Copy(&buf, resp.Body); err != nil {
		return nil, fmt.Errorf("failed to read response body: %w", err)
	}

	return &buf, nil
}

var _ provider.TtsProvider = (*BasetenProvider)(nil)
```

See [Custom Cloud Providers](/custom-cloud-providers) for deployment context and Baseten library links.

***

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