---
name: livevoice-api
description: >-
  Manage LiveVoice events, channels, AI translation, captions, screens,
  glossaries, webhooks, recordings, transcripts, invoices and statistics via the
  LiveVoice Server API v2. Use when the user wants to create, change, start,
  stop or look up anything in their LiveVoice account.
---

# LiveVoice Server API v2

Base URL: `https://livevoice.io/api/v2`
Full reference: https://livevoice.io/api/v2/openapi.yml (read it for the
exact fields of a resource before creating or updating it).

## API key

Read the key from the `LIVEVOICE_API_KEY` environment variable, or else
from the secret store of the user's OS, saved under the name
`livevoice-api`:

| OS      | Read the key                                    |
| ------- | ----------------------------------------------- |
| macOS   | `security find-generic-password -s livevoice-api -w` |
| Linux   | `secret-tool lookup service livevoice-api`      |
| Windows | `Get-Secret -Name livevoice-api -AsPlainText` (PowerShell) |

- Never print, echo or log the key, and never ask the user to paste it into
  the chat. Pass it inline, e.g.
  `-H "Authorization: Bearer $(security find-generic-password -s livevoice-api -w)"`,
  or
  in PowerShell
  `curl.exe -H "Authorization: Bearer $(Get-Secret -Name livevoice-api -AsPlainText)"`.
  Use `curl.exe`, since `curl` is an alias of `Invoke-WebRequest` in older
  PowerShell versions.
- If no key is found, ask the user to create one in their LiveVoice account
  settings and store it themselves. Each command asks for the key in a
  hidden prompt:

  - macOS: `security add-generic-password -s livevoice-api -a "$USER" -w`
    (add `-U` to replace an existing key)
  - Linux: `secret-tool store --label="LiveVoice API" service livevoice-api`
    (needs `libsecret-tools` and a desktop keyring)
  - Windows (PowerShell), the first three lines only once:
    ```powershell
    Install-Module Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore
    Register-SecretVault -Name SecretStore -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault
    Set-SecretStoreConfiguration -Authentication None
    Set-Secret -Name livevoice-api
    ```
    Without `-Authentication None`, reading the key asks for a vault
    password, which agents can't enter. The key stays encrypted for the
    Windows user.
  - Without a secret store (e.g. on a server), set `LIVEVOICE_API_KEY`
    instead.

## Rules

- Always ask the user before deleting anything, even when they asked for a
  cleanup. Name exactly what will be deleted (e.g. the event with its
  channels) and wait for a clear yes. Deleting is permanent.
- Send and receive JSON (`Content-Type: application/json`), except for
  image uploads and downloads (see below).
- Every response has `status` (`success` or `error`). Errors have `error`
  and optional `details`, nested like the fields, e.g.
  `{"geolocation": {"radius": ["inclusion"]}}`. Errors of list records are
  nested by their field, but not by index, e.g.
  `{"channel_groups": {"channel_ids": ["too_long"]}}`.
- Features are groups with an `enabled` field, e.g.
  `{"ai_translation": {"enabled": true, "languages": ["de-DE"]}}`. Disabled
  groups only render `{"enabled": false}`. Enabling a group again restores
  its previous settings, unless new ones are given.
- Groups require the feature of the same name in the user's plan, otherwise
  403 `feature_not_available` lists the missing features. Don't retry
  those, tell the user instead. `GET /account` lists the available
  `features`, check them before setting up larger things.
- Lists of records (glossary `sections`, screen `channel_groups`) replace all
  existing ones on update. Send the complete list.
- Fields marked `readOnly` (e.g. `id`, `participant_url`) can't be set.
- Rate limits: 60 requests per minute and 300 per hour, per IP address.
  A 429 has a plain text body, not JSON. Rejected requests count too, so
  don't retry in a loop. Wait for the next full minute, or tell the user if
  the hourly limit is reached. Prefer list endpoints over many single calls.

## Recipes

Create an event with an AI translation and start it:

1. `POST /events` with `{"name": "..."}`. Note `id` and `participant_url`.
2. `POST /events/{event_id}/channels` with `{"name": "English",
   "spoken_languages": ["en"], "audio_input": {"enabled": true}}`. This is
   the speaker's channel. AI channels can only translate channels with audio
   input.
3. Per target language, `POST /events/{event_id}/channels` with
   `{"name": "Deutsch", "ai_translation": {"enabled": true,
   "source_channel_id": 1, "languages": ["en"], "target_language": "de-DE",
   "voice": "female", "start_stop_mode": "manual"}}`.
4. `POST /channels/{id}/start-ai` on each translation channel. Stop it again
   with `stop-ai`. 422 `not_configured` means AI translation isn't enabled
   or the user has no free minutes or payment method left.

Show captions on a screen:

1. `GET /screen-presets` and pick a preset.
2. `POST /events/{event_id}/screens` with `{"name": "...", "preset_id": 1,
   "channel_groups": [{"type": "captions", "channel_ids": [2],
   "enabled": true}]}`. Share the returned `url`.

Captions group: one channel. To show several channels side by side (split
screen), use a scrolling preset and one group per channel, up to 3, e.g.
`"channel_groups": [{"type": "captions", "channel_ids": [2], "enabled":
true}, {"type": "captions", "channel_ids": [3], "enabled": true}]`. Groups
are shown in the given order: the first on the left, the next to its right
(or top to bottom, if the preset's split layout is horizontal). Disabled
groups are left out. A `cross_translation` group shows two translations of
the same multilingual source channel, each into one of its spoken languages,
in one column.

Use a glossary for AI services:

1. `POST /glossaries` with `{"name": "...", "sections": [{"type":
   "transcription", "from_language": "en", "entries": [{"from_token":
   "LiveVoice"}]}]}`.
2. `PATCH /events/{id}` with `{"glossary": {"enabled": true,
   "glossary_id": 3}}`.

Event images (requires branding): `PUT /events/{id}/header-image` or
`/logo-image` with the image as file field, e.g. `curl -X PUT -F
"file=@header.png"`. They are cropped to 2200x530 and 400x225 pixels.
`DELETE` the same URL to remove them.

Downloads return files, not JSON. Save them with `-o`:

- Recordings: `GET /channels/{channel_id}/recordings` lists them with their
  `formats`. `GET /recordings/{id}/download?format=mp3` redirects to the
  file, so follow it with `curl -L`.
- Transcripts: `GET /channels/{channel_id}/transcript?format=txt` (or `srt`,
  `vtt`, `csv`, `json`).
- Invoices: `GET /invoices` lists paid ones, `GET /invoices/{id}/download`
  returns the PDF.

Statistics: `GET /events/{id}/statistics` (or `/channels/{id}/statistics`,
`/account/statistics`) returns the peak participants per day, optionally
`?from=2026-10-01&to=2026-10-16`. Both are optional: `to` defaults to today,
`from` to 30 days before `to`. Statistics are kept for 14 months. Earlier or
future dates, or a `from` after `to`, are rejected as invalid.

Share links: an event renders `participant_url` and, if the operator is enabled,
`operator_url`. A channel renders `speaker_url`.
