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

# Dictionary and snippets

> Read and manage your custom dictionary and snippets from the API.

Your **dictionary** is the list of names, jargon and acronyms OpenWhispr hands to the transcription model as a hint, so they come out spelled the way you want. **Snippets** are trigger phrases that expand into saved text when you say them. The API reads and writes the same lists you edit under **Dictionary** in the desktop app — see [Teach OpenWhispr your words](/help/customise/custom-dictionary) and [Snippets](/help/customise/snippets) for how each behaves during dictation.

Typical uses: seed a new starter's dictionary from a team glossary, push product names into everyone's dictionary as they're added to your catalogue, or keep a standard set of snippets in sync from a script.

## Scopes

| Scope              | Access                                      |
| ------------------ | ------------------------------------------- |
| `dictionary:read`  | List dictionary entries.                    |
| `dictionary:write` | Add, rename, and delete dictionary entries. |
| `snippets:read`    | List snippets.                              |
| `snippets:write`   | Create, update, and delete snippets.        |

These routes take a personal key (`owk_live_`) only. A [workspace key](/api/workspace-keys) (`ow_wks_live_`) gets `403 forbidden` on every one of them — a dictionary belongs to a person, not a space.

Create the key in the desktop app or [through the API](/integrations/agent-setup), naming the scopes explicitly.

## Dictionary

An entry looks like this:

```json theme={null}
{
  "id": "...",
  "word": "Siobhán",
  "source": "manual",
  "created_at": "2026-09-01T10:12:00.000Z",
  "updated_at": "2026-09-01T10:12:00.000Z"
}
```

`source` is `manual` for words you added and `learned` for words [auto-learn](/help/customise/custom-dictionary#auto-learn-from-corrections) picked up from your corrections.

| Method   | Path                 | Scope              | What it does                     |
| -------- | -------------------- | ------------------ | -------------------------------- |
| `GET`    | `/dictionary/list`   | `dictionary:read`  | List entries, paginated.         |
| `POST`   | `/dictionary/create` | `dictionary:write` | Add up to 200 words in one call. |
| `PATCH`  | `/dictionary/{id}`   | `dictionary:write` | Change an entry's spelling.      |
| `DELETE` | `/dictionary/{id}`   | `dictionary:write` | Remove an entry.                 |

### List entries

```bash theme={null}
curl -H "Authorization: Bearer owk_live_YOUR_KEY" \
  "https://api.openwhispr.com/api/v1/dictionary/list?limit=100"
```

```json theme={null}
{
  "data": [
    { "id": "...", "word": "OpenWhispr", "source": "manual", "created_at": "...", "updated_at": "..." },
    { "id": "...", "word": "Kubernetes", "source": "learned", "created_at": "...", "updated_at": "..." }
  ],
  "has_more": false,
  "next_cursor": null
}
```

### Add words

```bash theme={null}
curl -X POST https://api.openwhispr.com/api/v1/dictionary/create \
  -H "Authorization: Bearer owk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"words": ["Siobhán", "SOC 2", "Kubernetes"]}'
```

Returns `201` with `data` holding an entry for every word you sent. The rules:

* **1 to 200 words** per call.
* Each word is **1 to 100 characters** and can't contain `<`, `>` or a line break.
* Words are **deduplicated case-insensitively**. A word already in your dictionary is returned as it stands, not created again — so re-sending a whole glossary is safe and won't leave duplicates.

### Rename an entry

```bash theme={null}
curl -X PATCH https://api.openwhispr.com/api/v1/dictionary/ENTRY_ID \
  -H "Authorization: Bearer owk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"word": "Siobhan"}'
```

Returns `200` with the updated entry. `404 not_found` if the id doesn't exist; `409 conflict` if another entry already has that spelling.

### Remove an entry

```bash theme={null}
curl -X DELETE https://api.openwhispr.com/api/v1/dictionary/ENTRY_ID \
  -H "Authorization: Bearer owk_live_YOUR_KEY"
```

Returns `204` with no body. Deletes are idempotent — deleting an id that's already gone also returns `204`, so retries are safe.

## Snippets

A snippet looks like this:

```json theme={null}
{
  "id": "...",
  "trigger": "cal link",
  "replacement": "https://cal.com/you/30min",
  "created_at": "2026-09-01T10:12:00.000Z",
  "updated_at": "2026-09-01T10:12:00.000Z"
}
```

| Method   | Path               | Scope            | What it does                                  |
| -------- | ------------------ | ---------------- | --------------------------------------------- |
| `GET`    | `/snippets/list`   | `snippets:read`  | List snippets, paginated.                     |
| `POST`   | `/snippets/create` | `snippets:write` | Create a snippet.                             |
| `PATCH`  | `/snippets/{id}`   | `snippets:write` | Change the trigger, the replacement, or both. |
| `DELETE` | `/snippets/{id}`   | `snippets:write` | Remove a snippet.                             |

### List snippets

```bash theme={null}
curl -H "Authorization: Bearer owk_live_YOUR_KEY" \
  "https://api.openwhispr.com/api/v1/snippets/list?limit=100"
```

Same envelope as the dictionary list: `data`, `has_more`, `next_cursor`.

### Create a snippet

```bash theme={null}
curl -X POST https://api.openwhispr.com/api/v1/snippets/create \
  -H "Authorization: Bearer owk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"trigger": "cal link", "replacement": "https://cal.com/you/30min"}'
```

Returns `201` with the snippet. `trigger` is 1 to 100 characters and must be unique — a repeat gets `409 conflict`. Two words make a safer trigger than one; see [choosing a good trigger](/help/customise/snippets#choosing-a-good-trigger).

### Update a snippet

```bash theme={null}
curl -X PATCH https://api.openwhispr.com/api/v1/snippets/SNIPPET_ID \
  -H "Authorization: Bearer owk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"replacement": "https://cal.com/you/45min"}'
```

Send `trigger`, `replacement`, or both — at least one is required. Returns `200` with the updated snippet.

### Remove a snippet

```bash theme={null}
curl -X DELETE https://api.openwhispr.com/api/v1/snippets/SNIPPET_ID \
  -H "Authorization: Bearer owk_live_YOUR_KEY"
```

Returns `204`, and is idempotent like the dictionary delete.

## Pagination

Both list endpoints take `limit` (1 to 500, default 200) and `cursor`. Most dictionaries fit in one page; if `has_more` is `true`, pass `next_cursor` back as `cursor` exactly as received. See [Pagination](/api/overview#pagination) for the general pattern.

## Organization policy

<Note>
  If you're a managed user and your organization has turned off cloud backup, the create and update endpoints for both dictionary and snippets return `403 forbidden` with the message "Cloud backup is turned off by your organization." Deletes are never blocked, so an integration can always clean up even when it can't add.
</Note>

## Errors

| HTTP status | Code                 | When                                                                                                                       |
| ----------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| 400         | `validation_error`   | Bad body — too many words, a word or trigger outside 1–100 characters, a forbidden character, or an update with no fields. |
| 400         | `limit_reached`      | You've hit a cap on the resource.                                                                                          |
| 403         | `forbidden`          | Missing scope, a workspace key, or the organization policy above. The `message` says which.                                |
| 404         | `not_found`          | No entry or snippet with that id, or it belongs to another user.                                                           |
| 405         | `method_not_allowed` | Wrong HTTP method — for example `GET /dictionary/create`.                                                                  |
| 409         | `conflict`           | Another entry already has that word, or another snippet already has that trigger.                                          |
| 500         | `internal_error`     | Something went wrong on our end. Retry after a short delay.                                                                |

All errors use the standard `{ "error": { "code", "message" } }` envelope — see [Errors](/api/errors).
