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

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 and pass it with option.WithAPIKey. Reading CAMB_API_KEY from the environment keeps secrets out of source control.
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")))
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.

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.
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.
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
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.
The ctx and c variables in the model tabs match the authenticated client pattern from the Quick Start and Authentication sections.

Voices

VoiceCloning.ListVoices returns the voices available to your account.
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 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). 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.
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 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).
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 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.
// 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.
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.
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 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.
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.
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.
// 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.
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.
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.
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 in the Go SDK repository for a complete BasetenProvider struct, HTTP wiring, and streaming output to disk.
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 for deployment context and Baseten library links.

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