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.

13 fields mapped, with a rollback plan

Prompt

Connect MCP
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.

  1. Azure Speech

    POST https://{region}.tts.speech.microsoft.com/cognitiveservices/v1

    Cartesia
    POST https://api.cartesia.ai/tts/bytes
  2. Azure Speech

    Ocp-Apim-Subscription-Key or Authorization: Bearer

    Cartesia
    X-API-Key and Cartesia-Version: 2026-08-14
  3. Azure Speech

    Content-Type: application/ssml+xml

    Cartesia
    Content-Type: application/json
  4. Azure Speech

    SSML <voice name="en-US-ChristopherNeural">

    Cartesia
    voice: {"id": "<voice id>"}
  5. Azure Speech

    Text inside the SSML body

    Cartesia
    transcript
    Note
    SSML-style <break>, <spell>, <emotion>, <speed> and <volume> tags work inside it.
  6. Azure Speech

    SSML xml:lang

    Cartesia
    language
  7. Azure Speech

    <prosody rate> and <prosody volume>

    Cartesia
    generation_config.speed (0.6 to 1.5) and volume (0.5 to 2.0)
  8. Azure Speech

    <mstts:express-as style="cheerful">

    Cartesia
    generation_config.emotion or an <emotion> tag
    Note
    styledegree and role have no equivalent. Map each style to the nearest emotion and listen.
  9. Azure Speech

    <phoneme> and <lexicon>

    Cartesia
    pronunciation_dict_id
  10. Azure Speech

    X-Microsoft-OutputFormat: raw-8khz-8bit-mono-mulaw

    Cartesia
    output_format: {"container": "raw", "encoding": "pcm_mulaw", "sample_rate": 8000}
  11. Azure Speech

    X-Microsoft-OutputFormat: riff-24khz-16bit-mono-pcm

    Cartesia
    output_format: {"container": "wav", "encoding": "pcm_s16le", "sample_rate": 24000}
  12. Azure Speech

    Speech SDK WordBoundary events

    Cartesia
    TTS WebSocket with add_timestamps
    Note
    /tts/bytes returns no timing. Sentence boundaries have no equivalent.
  13. Azure Speech

    10 minutes of audio per REST request

    Cartesia
    Split 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.

  1. Azure Speech

    Ocp-Apim-Subscription-Key with the resource key, or Authorization: Bearer with a token from issueToken

    Cartesia
    X-API-Key plus Cartesia-Version: 2026-08-14
    Note
    A token from issueToken is valid for 10 minutes. Microsoft's quickstart also sets SPEECH_REGION next to the key.
  2. Azure Speech

    SPEECH_KEY

    Cartesia
    CARTESIA_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.

  1. Model or plan

    Neural and Neural HD Flash voices

    Azure Speech
    $0.015
    Note
    $15 per million characters, East US pay as you go
  2. Model or plan

    Neural HD voices

    Azure Speech
    $0.022
    Note
    $22 per million characters, East US pay as you go
  3. Model or plan

    Professional Voice (custom neural) synthesis

    Azure Speech
    $0.024
    Note
    $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 pricing

Azure Speech limits compared with Cartesia

Published limits from both companies. Plans change them, so check both pages before you size a rollout.

  1. Limit

    Free tier

    Cartesia
    20,000 credits a month on the Free plan (about 20,000 characters)
    Azure Speech
    0.5 million Neural characters a month on the free F0 tier
  2. Limit

    Request limit

    Cartesia
    No published maximum; stream long text over the TTS WebSocket
    Azure Speech
    10 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 limits

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.

  1. Create a Cartesia API key, and keep the Azure resource running until cutover is done.

  2. Pick a voice per locale in the voice library and render the same scripts on both vendors.

  3. Replace the <speak> and <voice> wrapper with a JSON body. Keep <break> tags in the transcript, put speaking rate and volume in generation_config, and move pronunciation overrides into a pronunciation dictionary.

  4. Translate each X-Microsoft-OutputFormat value you use to the matching output_format from the table.

  5. If your code consumes word boundary or viseme events, move it to the TTS WebSocket or keep Azure for that path.

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.

Contact Sales

Start building.

Create an API key, then hand your coding agent the migration prompt and a few of your own scripts.

Try Cartesia