Migration guides / Azure Speech
Migrate Azure TTS from SSML to Cartesia
Moving off the Azure speech API turns an SSML document posted to a regional endpoint into one JSON request to Cartesia.
Migrate from Azure Speech with your coding agent
Paste this into Claude Code, Cursor or Codex in the repo that calls Azure Speech. It carries the field mapping, the Cartesia request and a rollout that keeps Azure Speech behind a flag until you switch.
Prompt
Move this codebase from Azure Speech to Cartesia for text to speech. Keep Azure Speech working behind a flag until I switch over.
## 1. Find the calls
Search for `SPEECH_KEY`. The request to replace is `POST https://{region}.tts.speech.microsoft.com/cognitiveservices/v1`. List every call site before you edit anything.
## 2. Target
- Request: `POST https://api.cartesia.ai/tts/bytes`
- Model: `model_id: "sonic-3.6"`
- Headers: `X-API-Key` read from the `CARTESIA_API_KEY` environment variable, and `Cartesia-Version: 2026-08-14`
- Auth swap: Azure Speech sends the key in `Ocp-Apim-Subscription-Key` with the resource key, or `Authorization: Bearer` with a token from `issueToken` from `SPEECH_KEY`. Replace it with `X-API-Key` from `CARTESIA_API_KEY` on the Cartesia path only, and keep `SPEECH_KEY` for the Azure Speech path. A token from `issueToken` is valid for 10 minutes. Microsoft's quickstart also sets `SPEECH_REGION` next to the key.
- Add `CARTESIA_API_KEY` wherever the project documents its environment (`.env.example`, README, deploy config). Never hardcode the key.
- Request size: Azure Speech caps a request at 10 minutes of audio per request, and Azure cuts longer output at 10 minutes; SSML holds up to 50 `<voice>` and `<audio>` tags. Cartesia publishes no per-request maximum, so keep any chunking the code already does and split at sentence boundaries; for long text, stream over the Cartesia TTS WebSocket.
- Concurrency: Azure Speech allows 20 transactions per 60 seconds on F0, not adjustable; 30 per second by default on S0, adjustable up to 1,000. Look up the Cartesia plan's concurrent request limit at https://www.cartesia.ai/pricing, cap parallel requests at it, and retry rate-limit errors with backoff.
- Voice IDs don't carry over. Keep each voice ID in one named constant or config value, put a placeholder where each Azure Speech voice was, and list them for me so I can pick replacements at https://www.cartesia.ai/voices.
- Suggested starting points to put in those placeholders, for me to confirm by ear: `en-US-ChristopherNeural` (male), the voice in the code above → `68fb6747-b6ea-4c44-a18e-4e29921424d3` (Derek - Deep Advisor); `en-US-AvaMultilingualNeural` (female) → `55deba52-bc73-4481-ab69-9c8831c8a7c3` (Camille - Friendly Expert); `de-DE-KatjaNeural` (female) → `9b4d08b6-0494-4301-ab92-9150f4ee2718` (Marlene - Elegant Speaker).
## 3. Field mapping, Azure Speech → Cartesia
- `POST https://{region}.tts.speech.microsoft.com/cognitiveservices/v1` → `POST https://api.cartesia.ai/tts/bytes`
- `Ocp-Apim-Subscription-Key` or `Authorization: Bearer` → `X-API-Key` and `Cartesia-Version: 2026-08-14`
- `Content-Type: application/ssml+xml` → `Content-Type: application/json`
- SSML `<voice name="en-US-ChristopherNeural">` → `voice`: `{"id": "<voice id>"}`
- Text inside the SSML body → `transcript`. SSML-style `<break>`, `<spell>`, `<emotion>`, `<speed>` and `<volume>` tags work inside it.
- SSML `xml:lang` → `language`
- `<prosody rate>` and `<prosody volume>` → `generation_config.speed` (0.6 to 1.5) and `volume` (0.5 to 2.0)
- `<mstts:express-as style="cheerful">` → `generation_config.emotion` or an `<emotion>` tag. `styledegree` and `role` have no equivalent. Map each style to the nearest emotion and listen.
- `<phoneme>` and `<lexicon>` → `pronunciation_dict_id`
- `X-Microsoft-OutputFormat: raw-8khz-8bit-mono-mulaw` → `output_format`: `{"container": "raw", "encoding": "pcm_mulaw", "sample_rate": 8000}`
- `X-Microsoft-OutputFormat: riff-24khz-16bit-mono-pcm` → `output_format`: `{"container": "wav", "encoding": "pcm_s16le", "sample_rate": 24000}`
- Speech SDK `WordBoundary` events → TTS WebSocket with `add_timestamps`. `/tts/bytes` returns no timing. Sentence boundaries have no equivalent.
- 10 minutes of audio per REST request → Split long scripts across several requests, or stream them over the WebSocket
## 4. Azure Speech fields with no Cartesia equivalent
- `<prosody pitch>` and `<prosody contour>`. What to do: Pick a voice that already sits at the pitch you want.
- Speech SDK `VisemeReceived` events and `<mstts:viseme>`. What to do: If you drive lip sync yourself, phoneme timestamps from `add_phoneme_timestamps` are the closest input.
## 5. Reference request
Match the language and HTTP client the codebase already uses. This is the same request in Python:
```python
import os
import requests
text = "We've received your claim. A specialist will call you within two business days."
VOICE_ID = "6ccbfb76-1fc6-48f7-b71d-91ac6298247b" # from /voices
response = requests.post(
"https://api.cartesia.ai/tts/bytes",
headers={
"X-API-Key": os.environ["CARTESIA_API_KEY"],
"Cartesia-Version": "2026-08-14",
},
json={
"model_id": "sonic-3.6",
"transcript": text,
"voice": {"id": VOICE_ID},
"language": "en",
"output_format": {
"container": "wav",
"encoding": "pcm_s16le",
"sample_rate": 24000,
},
},
)
response.raise_for_status()
open("out.wav", "wb").write(response.content)
```
## 6. Roll out
1. Choose Azure Speech or Cartesia per request from one config flag, defaulting to Azure Speech.
2. Run the same scripts through both and save the clips side by side for me to listen to. Check that the container, encoding and sample rate match what downstream code expects.
3. Leave the Azure Speech client, call and key in place so rollback is flipping the flag. Don't delete them in this change.
4. Run the existing tests, and add one that checks the Cartesia request body.
## Docs
- Azure text to speech REST API reference: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech
- Azure SSML voice and style reference: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-synthesis-markup-voice
- Azure Speech quotas and limits: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-services-quotas-and-limits
- Azure language and voice support: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=tts
- Azure text to speech quickstart (Python): https://learn.microsoft.com/en-us/azure/ai-services/speech-service/get-started-text-to-speech?pivots=programming-language-python
- Azure Speech release notes: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/releasenotes
- Cartesia TTS bytes API reference: https://docs.cartesia.ai/api-reference/tts/bytes
- Cartesia SSML tags: https://docs.cartesia.ai/build-with-cartesia/capability-guides/ssml-tags
When you're done, tell me which files changed, which fields you couldn't map, and how to flip the flag.Every Azure Speech field, mapped to Cartesia
The prompt carries this table. Each row is a field in your Azure Speech request and the Cartesia field that replaces it.
- Azure Speech
POST https:// {region}.tts.speech.microsoft.com/ cognitiveservices/ v1 CartesiaPOST https:// api.cartesia.ai/ tts/ bytes - Azure Speech
Ocp-Apim-Subscription-KeyorAuthorization: BearerCartesiaX-API-KeyandCartesia-Version: 2026-08-14 - Azure Speech
Content-Type: application/ssml+xml CartesiaContent-Type: application/json - Azure Speech
SSML
<voice name="en-US-ChristopherNeural">Cartesiavoice:{"id": "<voice id>"} - Azure Speech
Text inside the SSML body
CartesiatranscriptNoteSSML-style<break>,<spell>,<emotion>,<speed>and<volume>tags work inside it. - Azure Speech
SSML
xml:langCartesialanguage - Azure Speech
<prosody rate>and<prosody volume>Cartesiageneration_config.speed(0.6 to 1.5) andvolume(0.5 to 2.0) - Azure Speech
<mstts:express-as style="cheerful">Cartesiageneration_config.emotionor an<emotion>tagNotestyledegreeandrolehave no equivalent. Map each style to the nearest emotion and listen. - Azure Speech
<phoneme>and<lexicon>Cartesiapronunciation_dict_id - Azure Speech
X-Microsoft-OutputFormat: raw-8khz-8bit-mono-mulawCartesiaoutput_format:{"container": "raw", "encoding": "pcm_mulaw", "sample_rate": 8000} - Azure Speech
X-Microsoft-OutputFormat: riff-24khz-16bit-mono-pcmCartesiaoutput_format:{"container": "wav", "encoding": "pcm_s16le", "sample_rate": 24000} - Azure Speech
Speech SDK
WordBoundaryeventsCartesiaTTS WebSocket withadd_timestampsNote/tts/bytesreturns no timing. Sentence boundaries have no equivalent. - Azure Speech
10 minutes of audio per REST request
CartesiaSplit long scripts across several requests, or stream them over the WebSocket
Azure Speech API key and auth, mapped to Cartesia
Cartesia reads the key from the X-API-Key header and pins the API version with Cartesia-Version on every request.
- Azure Speech
Ocp-Apim-Subscription-Keywith the resource key, orAuthorization: Bearerwith a token fromissueTokenCartesiaX-API-KeyplusCartesia-Version: 2026-08-14NoteA token fromissueTokenis valid for 10 minutes. Microsoft's quickstart also setsSPEECH_REGIONnext to the key. - Azure Speech
SPEECH_KEYCartesiaCARTESIA_API_KEY
Azure Speech API pricing per 1,000 characters
List prices per 1,000 characters from the vendor's own pricing page. Plans, regions and volume discounts change what you pay.
- Model or plan
Neural and Neural HD Flash voices
Azure Speech$0.015Note$15 per million characters, East US pay as you go - Model or plan
Neural HD voices
Azure Speech$0.022Note$22 per million characters, East US pay as you go - Model or plan
Professional Voice (custom neural) synthesis
Azure Speech$0.024Note$24 per million characters; training and model hosting are billed on top
On Cartesia, usage draws from your plan's credits: 20,000 credits a month on the Free plan (about 20,000 characters). Enterprise pricing is custom.
Cartesia plans and pricingSources
- Azure Speech pricingazure.microsoft.com
- Azure Retail Prices API, Azure Speech in East USprices.azure.com
- Cartesia pricingcartesia.ai
Prices read from each company's pricing page in November 2026.
Azure Speech limits compared with Cartesia
Published limits from both companies. Plans change them, so check both pages before you size a rollout.
- Limit
Free tier
Cartesia20,000 credits a month on the Free plan (about 20,000 characters)Azure Speech0.5 million Neural characters a month on the free F0 tier - Limit
Request limit
CartesiaNo published maximum; stream long text over the TTS WebSocketAzure Speech10 minutes of audio per request, and Azure cuts longer output at 10 minutes; SSML holds up to 50<voice>and<audio>tags
Cartesia's figures come from its pricing page and voice library.
Cartesia pricing and plan limitsSources
- Azure Speech quotas and limitslearn.microsoft.com
- Azure Speech pricingazure.microsoft.com
- Azure text to speech REST API referencelearn.microsoft.com
Limits read from each company's documentation in November 2026.
Azure Speech voices and where to start on Cartesia
Voice names that join a locale and a name, such as en-US-ChristopherNeural. Each Speech resource lists its voices at /, and the language support page gives each one's gender.
Each card is a starting point for a common Azure Speech pick, matched by language and use. Listen before you choose.
What you get on Cartesia
What changes once the Azure Speech call is gone.
One endpoint, no regional resource
api.cartesia.ai/tts/bytes takes over from the per-region tts.speech.microsoft.com URL and the Speech resource behind it.
Settings as JSON fields
Voice, language, format, speed and volume are plain JSON keys. Your text needs no XML escaping, so a stray & can't break a request.
How to migrate from Azure Speech to Cartesia
The same changes the prompt makes, if you'd rather make them yourself.
Create a Cartesia API key, and keep the Azure resource running until cutover is done.
Pick a voice per locale in the voice library and render the same scripts on both vendors.
Replace the
<speak>and<voice>wrapper with a JSON body. Keep<break>tags in the transcript, put speaking rate and volume ingeneration_config, and move pronunciation overrides into a pronunciation dictionary.Translate each
X-Microsoft-OutputFormatvalue you use to the matchingoutput_formatfrom the table.If your code consumes word boundary or viseme events, move it to the TTS WebSocket or keep Azure for that path.
Sources
- Azure text to speech REST API referencelearn.microsoft.com
- Azure SSML voice and style referencelearn.microsoft.com
- Azure Speech quotas and limitslearn.microsoft.com
- Azure language and voice supportlearn.microsoft.com
- Azure text to speech quickstart (Python)learn.microsoft.com
- Azure Speech release noteslearn.microsoft.com
Request shapes checked against vendor documentation in November 2026.
Compare and read next
Frequently asked questions
How does Azure text to speech pricing work?
Azure bills neural TTS per million characters, at rates that vary by region and voice type; the table uses East US in US dollars. The free F0 tier includes 0.5 million Neural characters a month. Professional Voice adds training and hourly model hosting charges on top of synthesis.
What happens when I run out of free characters on Azure?
You can't buy more on F0. Microsoft's quotas page says F0 limits aren't adjustable, so the way past the 0.5 million monthly characters is to move the resource to Standard (S0), which bills per character. Microsoft notes that the new quotas can take several hours to apply after the switch, so change tiers before the allowance runs out.
What happens to my SSML?
The <speak> and <voice> wrapper turns into JSON fields. Inside the transcript, Cartesia reads SSML-style <break>, <spell>, <emotion>, <speed> and <volume> tags (SSML tags). Request-wide rate and volume go in generation_config, and pronunciations go in a dictionary you reference with pronunciation_dict_id.
What does an Azure TTS SSML example look like on Cartesia?
Take <voice name='en-US-ChristopherNeural'><prosody rate='+10%'>Your claim is approved.</prosody><break time='500ms'/>Payment goes out Friday.</voice>. On Cartesia the voice name becomes a voice ID, the rate becomes a generation_config.speed near 1.1 that you tune by ear, and the pause stays in the transcript as Your claim is approved.<break time="500ms"/>Payment goes out Friday.
Can I get 8 kHz mu-law for a phone line?
Yes. Set output_format to raw, pcm_mulaw and 8000.
What doesn't carry over from the Azure TTS API?
Pitch and contour, style degree and role, viseme events and sentence boundaries. Word timing comes back only over the WebSocket. Azure's per-voice speaking styles collapse into Cartesia's emotion set, so plan to retune each one.
How do I roll back a locale that sounds wrong on Cartesia?
Keep the Speech resource and its key live, and choose the vendor per locale in config. Move one locale at a time and compare call recordings or listener ratings. Sending a locale back to Azure is then a config change.
Get started today
Talk to an expert.
Connect with a member of our team and learn how Cartesia can help you build world-class voice experiences.
Start building.
Create an API key, then hand your coding agent the migration prompt and a few of your own scripts.