# Custom spelling

**The problem.** The model hears a word correctly but writes it its own way: "e mail" where your style guide says
"e-mail", "sequel" for "SQL", "gladia" without its capital. Custom spelling rewrites those variants into your
spelling, in every transcript, before you receive them.

**Where it works.** Every model.

> **Use the SDK.** The Gladia SDKs stream the audio, keep it until the API has transcribed it, and reconnect after a network drop
without losing a word. Most integration issues come from hand-written WebSocket clients: start from the SDK
examples below.

## Step by step

### 1. Map each spelling to its variants

```json
{
  "realtime_processing": {
    "custom_spelling": true,
    "custom_spelling_config": {
      "spelling_dictionary": { "SQL": ["sequel"], "e-mail": ["email", "e mail"] }
    }
  }
}
```

Each **key** is the spelling you want; its **values** are the variants to replace.

### 2. Start a session with it

Each program streams a 16-bit WAV file and prints what comes back. Put your API key in the `GLADIA_API_KEY` environment variable, then run `python custom_spelling.py meeting.wav`.

**Python SDK**

```python title="custom_spelling.py"
# pip install gladiaio-sdk
import os
import sys
import threading
import wave

from gladiaio_sdk import GladiaClient, LiveV2InitRequest

API_URL = os.environ.get("GLADIA_API_URL", "https://eu1.api.gladia.io")
API_KEY = os.environ["GLADIA_API_KEY"]

with wave.open(sys.argv[1]) as wav:  # a 16-bit PCM WAV file
    rate, channels = wav.getframerate(), wav.getnchannels()
    audio = wav.readframes(wav.getnframes())

config = {
    "encoding": "wav/pcm",
    "sample_rate": rate,
    "bit_depth": 16,
    "channels": channels,
    "model": "solaria-3",
    "language_config": {
        "languages": [
            "en"
        ]
    },
    "realtime_processing": {
        "custom_spelling": True,
        "custom_spelling_config": {
            "spelling_dictionary": {
                "Gladia": [
                    "gladya",
                    "glad ia"
                ],
                "SQL": [
                    "sequel"
                ],
                "e-mail": [
                    "email",
                    "e mail"
                ]
            }
        }
    }
}

client = GladiaClient(api_key=API_KEY, api_url=API_URL)
session = client.live_v2().start_session(LiveV2InitRequest.from_dict(config))
ended, errors = threading.Event(), []

@session.on("message")
def on_message(message):
    if message.type == "transcript" and message.data.is_final:
        print(message.data.utterance.text)

session.on("error", errors.append)
session.once("ended", lambda _: ended.set())

step = rate * channels * 2 // 10  # 100 ms of audio per chunk
for start in range(0, len(audio), step):
    session.send_audio(audio[start : start + step])  # live audio: send each chunk as it is captured
session.stop_recording()

ended.wait(timeout=60)
sys.exit(1 if errors or not ended.is_set() else 0)
```

**JavaScript SDK**

```js title="custom_spelling.mjs"
// npm install @gladiaio/sdk
import { readFileSync } from "node:fs";
import { GladiaClient } from "@gladiaio/sdk";

const { sampleRate, channels, pcm } = readWav(process.argv[2]); // a 16-bit PCM WAV file

const config = {
  encoding: "wav/pcm",
  sample_rate: sampleRate,
  bit_depth: 16,
  channels,
  model: "solaria-3",
  language_config: {
    languages: [
      "en"
    ]
  },
  realtime_processing: {
    custom_spelling: true,
    custom_spelling_config: {
      spelling_dictionary: {
        Gladia: [
          "gladya",
          "glad ia"
        ],
        SQL: [
          "sequel"
        ],
        "e-mail": [
          "email",
          "e mail"
        ]
      }
    }
  }
};

const client = new GladiaClient({
  apiKey: process.env.GLADIA_API_KEY,
  apiUrl: process.env.GLADIA_API_URL ?? "https://eu1.api.gladia.io",
});
const session = client.liveV2().startSession(config);

session.on("message", (message) => {
  if (message.type === "transcript" && message.data.is_final) console.log(message.data.utterance.text);
});
session.on("error", (error) => {
  console.error(error);
  process.exitCode = 1;
});
session.once("ended", () => process.exit());

const step = (sampleRate * channels * 2) / 10; // 100 ms of audio per chunk
for (let start = 0; start < pcm.length; start += step) {
  session.sendAudio(pcm.subarray(start, start + step)); // live audio: send each chunk as it is captured
}
session.stopRecording();

function readWav(path) {
  const file = readFileSync(path);
  let fmt, pcm;
  for (let pos = 12; pos < file.length; ) {
    const id = file.toString("ascii", pos, pos + 4);
    const size = file.readUInt32LE(pos + 4);
    if (id === "fmt ") fmt = { channels: file.readUInt16LE(pos + 10), sampleRate: file.readUInt32LE(pos + 12) };
    if (id === "data") pcm = file.subarray(pos + 8, pos + 8 + size);
    pos += 8 + size + (size % 2);
  }
  return { ...fmt, pcm };
}
```

**HTTP**

Create the session, then stream its audio to the returned `url` ([Live WebSocket](/api-reference/v2/live/websocket/)):

```bash
curl -X POST https://eu1.api.gladia.io/v2/live \
  -H "x-gladia-key: $GLADIA_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "encoding": "wav/pcm",
    "sample_rate": 16000,
    "bit_depth": 16,
    "channels": 1,
    "model": "solaria-3",
    "language_config": {
      "languages": [
        "en"
      ]
    },
    "realtime_processing": {
      "custom_spelling": true,
      "custom_spelling_config": {
        "spelling_dictionary": {
          "Gladia": [
            "gladya",
            "glad ia"
          ],
          "SQL": [
            "sequel"
          ],
          "e-mail": [
            "email",
            "e mail"
          ]
        }
      }
    }
  }'
```

### 3. Check the result

```json
{ "type": "transcript", "data": { "is_final": true, "utterance": { "text": "Send the SQL query by e-mail to Gladia." } } }
```

The rewrite applies to partial and final transcripts, and to the text of their words.

## Limits and gotchas

- **Whole words, any case.** "sequel" is rewritten, "sequels" is not. Add each form you need as a variant.
- **The longest variant wins.** With "e mail" and "mail" both listed, "e mail" is rewritten first.
- **It does not change what is recognised.** If the model writes something else entirely, there is nothing to
  rewrite: help it hear the word with [custom vocabulary](/features/custom-vocabulary/), then fix the spelling here.
- **Values must be lists.** `{"SQL": "sequel"}` is refused (`400`): write `{"SQL": ["sequel"]}`.
- **Translations are not rewritten.** [Translation](/features/translation/) works from the audio, not from the
  transcript.
- **Size limit.** The spelling dictionary shares the session's URL budget with the vocabulary and
  `custom_metadata`: a few hundred entries in all, else `400` "the session configuration is too large…".
- **Entities keep their tags.** With [entities](/features/named-entity-recognition/) or [PII
  redaction](/features/pii-redaction/), the rewrite never touches the inline elements, only the words.

## Troubleshooting

| symptom | cause | fix |
|---|---|---|
| a variant is not rewritten | it is part of a longer word, or a different form | add that exact form as a variant |
| `400` on `custom_spelling_config` | a value is a string, not a list | wrap it in a list |
| the spelling flag is on, nothing changes | `custom_spelling_config` missing, or the variant is never written by the model | send the dictionary; check the raw transcript first |
