> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kugelaudio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Dictionaries

> Manage per-project custom word dictionaries

Custom dictionaries let you control how the TTS pipeline pronounces
specific words. Each dictionary is scoped to a project and contains
word → replacement/IPA mappings that are applied before synthesis.

Use these endpoints to sync dictionaries from your own data sources
(PIM, CMS, internal glossary). The TTS-side cache is invalidated after
every mutation, so the next synthesis request picks up your changes
immediately.

<Note>
  Authentication uses your project-scoped API key. Master-key callers
  must supply `?project_id=<id>` on every request because the master
  key is not pinned to a project.
</Note>

***

## List Dictionaries

Return every dictionary in the caller's project.

<ParamField path="GET" method="/v1/dictionaries" />

### Query Parameters

<ParamField query="project_id" type="integer">
  Required for master-key callers; rejected for project-scoped keys
  whose value disagrees with the key's project.
</ParamField>

### Response

```json theme={null}
{
  "dictionaries": [
    {
      "id": 1,
      "project_id": 42,
      "name": "Brand names",
      "description": "Customer product names",
      "language": "en",
      "is_active": true,
      "created_at": "2026-05-20T09:00:00+00:00",
      "updated_at": "2026-05-20T09:05:00+00:00"
    }
  ]
}
```

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.kugelaudio.com/v1/dictionaries" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```python Python theme={null}
  from kugelaudio import KugelAudio

  client = KugelAudio(api_key="YOUR_API_KEY")
  for d in client.dictionaries.list():
      print(f"{d.id}: {d.name} ({d.language})")
  ```

  ```typescript JavaScript theme={null}
  import { KugelAudio } from 'kugelaudio';

  const client = new KugelAudio({ apiKey: 'YOUR_API_KEY' });
  const dictionaries = await client.dictionaries.list();
  for (const d of dictionaries) {
    console.log(`${d.id}: ${d.name} (${d.language})`);
  }
  ```

  ```java Java theme={null}
  KugelAudio client = KugelAudio.fromEnv();
  for (Dictionary d : client.dictionaries().list()) {
      System.out.println(d.getId() + ": " + d.getName());
  }
  ```
</CodeGroup>

***

## Create Dictionary

<ParamField path="POST" method="/v1/dictionaries" />

### Body

<ParamField body="name" type="string" required>
  Display name. Must be unique within the project.
</ParamField>

<ParamField body="description" type="string">
  Free-form description.
</ParamField>

<ParamField body="language" type="string">
  BCP-47 language tag (`en`, `de-DE`, ...). Omit for all languages.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.kugelaudio.com/v1/dictionaries" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"name": "Brand names", "language": "en"}'
  ```

  ```python Python theme={null}
  d = client.dictionaries.create(name="Brand names", language="en")
  print(d.id)
  ```

  ```typescript JavaScript theme={null}
  const d = await client.dictionaries.create({ name: 'Brand names', language: 'en' });
  console.log(d.id);
  ```

  ```java Java theme={null}
  Dictionary d = client.dictionaries().create("Brand names", null, "en");
  System.out.println(d.getId());
  ```
</CodeGroup>

***

## Get Dictionary

<ParamField path="GET" method="/v1/dictionaries/{dict_id}" />

Returns the dictionary record. Returns `403 Forbidden` if the dictionary
belongs to a project your API key is not scoped to.

***

## Update Dictionary

<ParamField path="PATCH" method="/v1/dictionaries/{dict_id}" />

Only the provided fields are changed.

### Body

<ParamField body="name" type="string" />

<ParamField body="description" type="string" />

<ParamField body="language" type="string" />

<ParamField body="is_active" type="boolean">
  Disable a dictionary without deleting it.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH "https://api.kugelaudio.com/v1/dictionaries/1" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"is_active": false}'
  ```

  ```python Python theme={null}
  client.dictionaries.update(1, is_active=False)
  ```

  ```typescript JavaScript theme={null}
  await client.dictionaries.update(1, { isActive: false });
  ```

  ```java Java theme={null}
  client.dictionaries().update(1, null, null, null, false);
  ```
</CodeGroup>

***

## Delete Dictionary

<ParamField path="DELETE" method="/v1/dictionaries/{dict_id}" />

Deletes the dictionary and all its entries.

### Response

```json theme={null}
{ "deleted": true }
```

***

## List Entries

<ParamField path="GET" method="/v1/dictionaries/{dict_id}/entries" />

### Query Parameters

<ParamField query="search" type="string">
  Case-insensitive substring filter on `word`.
</ParamField>

<ParamField query="limit" type="integer" default="100">
  Page size, 1-500.
</ParamField>

<ParamField query="offset" type="integer" default="0">
  Pagination offset.
</ParamField>

### Response

```json theme={null}
{
  "entries": [
    {
      "id": 11,
      "dictionary_id": 1,
      "word": "Postgres",
      "replacement": "post-gres",
      "ipa": null,
      "case_sensitive": false,
      "created_at": "2026-05-20T09:00:00+00:00",
      "updated_at": "2026-05-20T09:00:00+00:00"
    }
  ],
  "total": 12,
  "limit": 100,
  "offset": 0
}
```

***

## Add Entry

<ParamField path="POST" method="/v1/dictionaries/{dict_id}/entries" />

### Body

<ParamField body="word" type="string" required>
  Word to match (≤ 200 chars).
</ParamField>

<ParamField body="replacement" type="string" required>
  Text the engine pronounces instead (≤ 1000 chars).
</ParamField>

<ParamField body="ipa" type="string">
  Optional IPA transcription. Takes precedence over `replacement` when set.
</ParamField>

<ParamField body="case_sensitive" type="boolean" default="false">
  Match the original case exactly.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.kugelaudio.com/v1/dictionaries/1/entries" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"word": "Postgres", "replacement": "post-gres"}'
  ```

  ```python Python theme={null}
  e = client.dictionaries.entries.add(
      dictionary_id=1,
      word="Postgres",
      replacement="post-gres",
  )
  ```

  ```typescript JavaScript theme={null}
  const e = await client.dictionaries.entries.add(1, {
    word: 'Postgres',
    replacement: 'post-gres',
  });
  ```

  ```java Java theme={null}
  DictionaryEntry e = client.dictionaries().entries().add(
      1, new DictionaryEntryInput("Postgres", "post-gres"));
  ```
</CodeGroup>

***

## Bulk Replace Entries

<ParamField path="PUT" method="/v1/dictionaries/{dict_id}/entries" />

Atomically replace every entry in the dictionary. Entries currently in
the dictionary whose `word` is not in the supplied list are deleted.
Idempotent — calling twice with the same payload converges to the same
final state.

### Body

<ParamField body="entries" type="array" required>
  Array of `{ word, replacement, ipa?, case_sensitive? }` items.
  Duplicate `word` values within the payload are rejected.
</ParamField>

### Response

```json theme={null}
{ "upserted": 25, "deleted": 3, "total": 25 }
```

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT "https://api.kugelaudio.com/v1/dictionaries/1/entries" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "entries": [
        {"word": "Postgres",   "replacement": "post-gres"},
        {"word": "Kubernetes", "replacement": "koo-ber-net-eez"}
      ]
    }'
  ```

  ```python Python theme={null}
  result = client.dictionaries.entries.replace_all(
      dictionary_id=1,
      entries=[
          {"word": "Postgres",   "replacement": "post-gres"},
          {"word": "Kubernetes", "replacement": "koo-ber-net-eez"},
      ],
  )
  print(result.upserted, result.deleted)
  ```

  ```typescript JavaScript theme={null}
  const result = await client.dictionaries.entries.replaceAll(1, [
    { word: 'Postgres',   replacement: 'post-gres' },
    { word: 'Kubernetes', replacement: 'koo-ber-net-eez' },
  ]);
  console.log(result.upserted, result.deleted);
  ```

  ```java Java theme={null}
  BulkReplaceResult result = client.dictionaries().entries().replaceAll(
      1,
      List.of(
          new DictionaryEntryInput("Postgres",   "post-gres"),
          new DictionaryEntryInput("Kubernetes", "koo-ber-net-eez")));
  ```
</CodeGroup>

***

## Update Entry

<ParamField path="PATCH" method="/v1/dictionaries/{dict_id}/entries/{entry_id}" />

Only non-null fields are sent.

### Body

<ParamField body="word" type="string" />

<ParamField body="replacement" type="string" />

<ParamField body="ipa" type="string" />

<ParamField body="case_sensitive" type="boolean" />

***

## Delete Entry

<ParamField path="DELETE" method="/v1/dictionaries/{dict_id}/entries/{entry_id}" />

### Response

```json theme={null}
{ "deleted": true }
```

***

## Error responses

| Status | Code               | Meaning                                                              |
| ------ | ------------------ | -------------------------------------------------------------------- |
| 400    | `VALIDATION_ERROR` | Missing or invalid field; e.g. master-key call without `project_id`. |
| 401    | `UNAUTHORIZED`     | API key missing, invalid, or not project-scoped.                     |
| 403    | `UNAUTHORIZED`     | API key is scoped to a different project than the target.            |
| 404    | `NOT_FOUND`        | Dictionary or entry does not exist.                                  |
