> ## Documentation Index
> Fetch the complete documentation index at: https://heygen-1fa696a7.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Brand Glossary

> Control how HeyGen says your custom terms. Create a brand glossary over the API, keep its term list up to date, and pass its brand_glossary_id so product names and numbers are spoken correctly and survive translation.

<img className="w-full h-44 object-cover rounded-xl" src="https://mintcdn.com/heygen-1fa696a7/hfMXXwJzjE7vBSYZ/images/theme/research.webp?fit=max&auto=format&n=hfMXXwJzjE7vBSYZ&q=85&s=3e4c2594813dcfd56985f3d63e3ba321" alt="" noZoom width="1400" height="788" data-path="images/theme/research.webp" />

A brand glossary is a list of your terms and the pronunciation each one should get. Pass its `brand_glossary_id` when you create a video and every term in the list is respelled for the voice — so `HeyGen` is spoken `hey-jen` rather than *hey-gen*, and an acronym your team says as a word isn't spelled out letter by letter.

Glossaries can be authored in the HeyGen app under **Brand Kit** or created and edited over the API. Creating them over the API is what lets each of your own customers get their own vocabulary: mint a glossary per tenant, keep it in sync with their product catalog, and pass its ID on every render.

<Info>
  **API reference:** [Create Brand Glossary](/reference/create-brand-glossary) · [Update Brand Glossary](/reference/update-brand-glossary) · [Get Brand Glossary](/reference/get-brand-glossary) · [List Brand Glossaries](/reference/list-brand-glossaries)
</Info>

## One glossary, two jobs

The same glossary record behaves differently depending on where you pass it, because the two pipelines need different things from it:

* **In generation** — [Studio](/studio-videos), [Templates](/templates), and single-scene `avatar` and `image` videos — it sets **pronunciation**. The term keeps its spelling in the script and in captions; only the audio changes.
* **In [translation](/docs/video-translate)** — it pins **terminology**. A term that a general translator would render literally stays your term instead, so `Reformer` arrives as the Pilates equipment rather than a political activist.

One list serves both, so a term you add for a Studio voiceover is also honored the next time you translate that video.

## Create a glossary

[`POST /v3/brand-glossaries`](/reference/create-brand-glossary) takes a `name` and an optional list of `terms`. Include the terms to populate the glossary in the same call, or omit them and fill the list in later.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST "https://api.heygen.com/v3/brand-glossaries" \
    -H "X-Api-Key: $HEYGEN_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
    -d '{
      "name": "Acme Product Names",
      "terms": [
        { "term": "HeyGen", "pronunciation": "hey-jen" },
        { "term": "2024", "pronunciation": "twenty twenty-four" }
      ]
    }'
  ```

  ```python Python theme={null}
  import requests, uuid

  resp = requests.post(
      "https://api.heygen.com/v3/brand-glossaries",
      headers={
          "X-Api-Key": HEYGEN_API_KEY,
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json={
          "name": "Acme Product Names",
          "terms": [
              {"term": "HeyGen", "pronunciation": "hey-jen"},
              {"term": "2024", "pronunciation": "twenty twenty-four"},
          ],
      },
  )
  brand_glossary_id = resp.json()["data"]["brand_glossary_id"]
  ```

  ```javascript Node.js theme={null}
  const resp = await fetch("https://api.heygen.com/v3/brand-glossaries", {
    method: "POST",
    headers: {
      "X-Api-Key": process.env.HEYGEN_API_KEY,
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({
      name: "Acme Product Names",
      terms: [
        { term: "HeyGen", pronunciation: "hey-jen" },
        { term: "2024", pronunciation: "twenty twenty-four" },
      ],
    }),
  });
  const { data } = await resp.json();
  ```
</CodeGroup>

| Parameter               | Type   | Required | Description                                                                                                                                                  |
| ----------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name`                  | string | Yes      | Display name. Surrounding whitespace is trimmed; the trimmed name must be 1–64 characters and unique within your workspace, compared without regard to case. |
| `terms`                 | array  | No       | Term-to-pronunciation mappings. Omit it, or pass an empty array, to create a glossary you populate later.                                                    |
| `terms[].term`          | string | Yes      | The term as written in a script. Matched case-insensitively.                                                                                                 |
| `terms[].pronunciation` | string | Yes      | The respelling handed to the voice in place of `term`, for example `hey-jen`.                                                                                |

Send an `Idempotency-Key` header so a retry is safe: a repeat within 24 hours replays the original response rather than creating a second glossary.

`201` returns the full glossary, the same shape you read back later:

```json theme={null}
{
  "data": {
    "brand_glossary_id": "8c2d5e91a47b4f3c8d1e6a9b2f5c7d40",
    "name": "Acme Product Names",
    "created_at": "2026-03-10T18:22:22",
    "updated_at": "2026-03-10T18:22:22",
    "terms": [
      { "term": "HeyGen", "pronunciation": "hey-jen" },
      { "term": "2024", "pronunciation": "twenty twenty-four" }
    ]
  }
}
```

The glossary is usable immediately — there is no build step to wait on. Pass `brand_glossary_id` on your next creation request and it takes effect on that render.

### Choosing terms

A pronunciation is a respelling, not a phonetic alphabet: write the term the way you would coach a person to say it, in syllables the voice can read. Hyphens and spaces are how you break it up.

* **Brand and product names** — `HeyGen` → `hey-jen`, `HyperFrames` → `hyper frames`.
* **Acronyms your team says as a word** — an acronym spelled out letter by letter when you say it as a word is a glossary entry, and so is the reverse.
* **Numerals, years, and units** — a written `2024` can be read several defensible ways, and a glossary is how you pick one.

Terms the voice already reads correctly need no entry; add a term when you have heard it come out wrong, and confirm the fix by rendering the same script with and without the glossary.

## Update a glossary

[`PATCH /v3/brand-glossaries/{brand_glossary_id}`](/reference/update-brand-glossary) edits an existing glossary. Each field is replaced independently:

* A field you **omit** is left untouched.
* A field you **send** replaces that value in full.
* An **empty `terms` array** removes every term.

```bash theme={null}
curl -X PATCH "https://api.heygen.com/v3/brand-glossaries/8c2d5e91a47b4f3c8d1e6a9b2f5c7d40" \
  -H "X-Api-Key: $HEYGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Product Names (2026)"
  }'
```

The response is the same `data` object the create call returns, with `updated_at` moved forward. Send at least one of `name` or `terms`; an empty body is a `400` with `"Provide at least one field to update."`

### Adding one term

`terms` is replace-in-full, so adding a single term means reading the glossary, appending to the list, and sending the whole list back. Read first every time rather than from a cached copy — a teammate editing the same glossary in the app is otherwise overwritten.

```python theme={null}
import requests

base = "https://api.heygen.com/v3/brand-glossaries/8c2d5e91a47b4f3c8d1e6a9b2f5c7d40"
headers = {"X-Api-Key": HEYGEN_API_KEY}

current = requests.get(base, headers=headers).json()["data"]["terms"]
current.append({"term": "Reformer", "pronunciation": "ree-form-er"})

requests.patch(base, headers=headers, json={"terms": current})
```

<Note>
  A glossary can also carry **translation rules**, which control how terms are handled when a video is translated. Those are managed in the HeyGen app under **Brand Kit** and are never modified by a `PATCH` here, so a glossary edited in the app keeps them through an update made over the API.
</Note>

## Find and inspect glossaries

[`GET /v3/brand-glossaries`](/reference/list-brand-glossaries) returns the glossaries in the authenticated workspace.

| Parameter | Type    | Default | Description                                            |
| --------- | ------- | ------- | ------------------------------------------------------ |
| `limit`   | integer | 10      | Results per page, 1–100.                               |
| `token`   | string  | —       | Opaque cursor from a previous response's `next_token`. |

```bash theme={null}
curl "https://api.heygen.com/v3/brand-glossaries" \
  -H "X-Api-Key: $HEYGEN_API_KEY"
```

```json theme={null}
{
  "data": [
    {
      "brand_glossary_id": "8c2d5e91a47b4f3c8d1e6a9b2f5c7d40",
      "name": "Acme Product Names",
      "created_at": "2026-03-10T18:22:22",
      "updated_at": "2026-06-19T17:34:52"
    }
  ],
  "has_more": false,
  "next_token": null
}
```

The list gives you names, IDs, and timestamps but not contents — `updated_at` is how you tell which of several similarly named glossaries your team still maintains.

[`GET /v3/brand-glossaries/{brand_glossary_id}`](/reference/get-brand-glossary) returns one glossary with its full term list. This is the endpoint to reach for when a generated video pronounces something unexpectedly — it tells you whether the term is in the list at all, and exactly what respelling it was given.

```bash theme={null}
curl "https://api.heygen.com/v3/brand-glossaries/8c2d5e91a47b4f3c8d1e6a9b2f5c7d40" \
  -H "X-Api-Key: $HEYGEN_API_KEY"
```

<Note>
  A glossary with `"terms": []` is valid and applying it is accepted, but it changes nothing. An empty term list is the usual explanation for a glossary that appears to have no effect.
</Note>

## Apply a glossary

Pass `brand_glossary_id` at the top level of the creation request. It is global to the request — one glossary per call.

<CodeGroup>
  ```bash Studio theme={null}
  curl -X POST "https://api.heygen.com/v3/videos" \
    -H "X-Api-Key: $HEYGEN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "type": "studio",
      "brand_glossary_id": "8c2d5e91a47b4f3c8d1e6a9b2f5c7d40",
      "scenes": [
        {
          "type": "avatar_video",
          "input": {
            "type": "avatar",
            "avatar_id": "YOUR_AVATAR_ID",
            "script": "HeyGen shipped this in 2024.",
            "voice_id": "YOUR_VOICE_ID"
          }
        }
      ]
    }'
  ```

  ```bash Avatar theme={null}
  curl -X POST "https://api.heygen.com/v3/videos" \
    -H "X-Api-Key: $HEYGEN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "type": "avatar",
      "brand_glossary_id": "8c2d5e91a47b4f3c8d1e6a9b2f5c7d40",
      "avatar_id": "YOUR_AVATAR_ID",
      "script": "HeyGen shipped this in 2024.",
      "voice_id": "YOUR_VOICE_ID"
    }'
  ```

  ```bash Translation theme={null}
  curl -X POST "https://api.heygen.com/v3/video-translations" \
    -H "X-Api-Key: $HEYGEN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "video": { "type": "url", "url": "https://example.com/source.mp4" },
      "output_languages": ["Spanish (Spain)"],
      "brand_glossary_id": "8c2d5e91a47b4f3c8d1e6a9b2f5c7d40"
    }'
  ```

  ```bash Template theme={null}
  curl -X POST "https://api.heygen.com/v3/templates/$TEMPLATE_ID" \
    -H "X-Api-Key: $HEYGEN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "brand_glossary_id": "8c2d5e91a47b4f3c8d1e6a9b2f5c7d40",
      "variables": {}
    }'
  ```
</CodeGroup>

See [On Brand](/docs/on-brand) for the full list of endpoints that accept the field. On [`POST /v3/videos`](/reference/create-video) it applies to `"type"` of `studio`, `avatar`, or `image`, and in a [translation batch](/batch-video-translations) or a [video batch](/batch-videos) it is set per item.

### What a glossary leaves alone

* **Captions and subtitles keep the original spelling.** A term written `HeyGen` still reads `HeyGen` on screen and in the `.srt` sidecar while being spoken `hey-jen`. The respelling is for the voice, not the viewer.
* **Scenes with finished audio are untouched.** A Studio scene that supplies `audio_url` or `audio_asset_id` already has its audio, so there is no synthesis step for the glossary to influence.

## Errors

| Status | Code                  | Meaning                                                                                                                                                                                                  |
| ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `invalid_parameter`   | A field is out of range — for example `"'name' must be between 1 and 64 characters."` The `param` field names the offending field.                                                                       |
| `404`  | `resource_not_found`  | No glossary with that ID in the authenticated workspace.                                                                                                                                                 |
| `409`  | `conflict`            | `"A brand glossary with this name already exists."` Names are unique per workspace, compared without regard to case — [list your glossaries](/reference/list-brand-glossaries) to find the existing one. |
| `409`  | `request_in_progress` | A request with this `Idempotency-Key` is still running. Retry shortly.                                                                                                                                   |
| `429`  | `rate_limit_exceeded` | Retry after the duration in the `Retry-After` header.                                                                                                                                                    |

On a creation request, an unknown or inaccessible `brand_glossary_id` is rejected with `400` when the request is made, rather than failing partway through a render — so a bad ID produces no video and consumes no credits. Glossaries are workspace-scoped, so if an ID you can see in the app is rejected, check that the API key belongs to that same workspace.

## `brand_voice_id` is the former name

`brand_voice_id` was the original name for this field and is **deprecated**. It is accepted on three endpoints — [`POST /v3/templates/{template_id}`](/reference/generate-video-from-template), [`POST /v3/video-translations`](/reference/create-video-translation), and [`POST /v3/video-translations/proofreads`](/reference/create-proofread-session) — where it resolves to the same workspace record, so existing requests keep working.

It is **not** accepted on [`POST /v3/videos`](/reference/create-video), on any composition type. That request rejects unknown fields outright, so `brand_voice_id` there is a `400` rather than a silently ignored field — use `brand_glossary_id`. This is the trap for anyone porting a working translation payload over to Studio.

Prefer `brand_glossary_id` everywhere in new code. The old name is also misleading: a glossary never changes which voice speaks.
