---
title: "Synthesise (or fetch from cache) the clips a route will speak."
method: POST
path: "/v1/voice/clips"
tags: ["voice"]
---

# Synthesise (or fetch from cache) the clips a route will speak.

`POST /v1/voice/clips`

## Request

- Base URL: `https://api.mapmap.ai`
- URL: `https://api.mapmap.ai/v1/voice/clips`
- Auth: one of:
  - HTTP bearer
  - API key in query parameter `api_key`

## Request body

- VoiceClipsRequest: The `POST /v1/voice/clips` request body.
  - `format` string, required: Container: `opus` (Ogg Opus 48 kHz, for Android) or `m4a` (MPEG-4 AAC 44.1 kHz, for iOS, which cannot play Ogg Opus).
  - `gender` string, nullable: `female` or `male`, alongside `locale`: that locale's default voice of this gender. With `voice`, the voice must be of this gender. Needs `locale` or `voice`.
  - `locale` string, nullable: BCP 47 locale (`en-GB`, `fr-FR`), answered by that locale's default voice for `gender`, or for the locale's default gender (female) when `gender` is absent. With neither `voice` nor `locale` the voice is the en-GB default (`en-gb-f`).
  - `providers` string[], nullable: Provider classes this request may be answered by: `elevenlabs`, `open` (the self-hosted open-source voices) or both. A voice outside them is replaced by its `fallback`; `["open"]` never touches ElevenLabs. Absent allows both.
  - `utterances` UtteranceRequest[], required: The sentences to speak, at most [`MAX_UTTERANCES`].
    - `rate` number, double, nullable: Speaking rate, [`MIN_RATE`]–[`MAX_RATE`] (default 1.0), rounded half up to two decimals. Part of the cache key, because it changes the audio.
    - `text` string, required: The whole sentence, at most [`MAX_TEXT_CHARS`] characters after normalisation. One clip is one whole sentence: clips are never concatenated at playback, so a half-sentence is not a clip.
  - `voice` string, nullable: Registry voice id (`en-gb-f`, `amelia`, `fr-fr-m`). Wins over `locale`; when both are sent the voice must speak that locale.

## Response `200`

Clip URLs, cache flags and durations; `pending` for clips still being synthesised

- VoiceClipsResponse: The `POST /v1/voice/clips` response.
  - `clips` ClipAnswer[], required: One entry per requested utterance, in request order.
    - `cached` boolean, required: `true` when the clip already existed, so the caller was not billed for it.
    - `duration_ms` integer, nullable: Measured duration. Absent while `pending`, or on failure.
    - `error` string, nullable: Why this clip will never arrive. Present only on a provider failure, and mutually exclusive with `pending`. An addition to the agreed contract, because "pending forever" would have been the alternative and that is a lie a client cannot act on.
    - `fallback` boolean: `true` when the requested voice failed (ElevenLabs quota, payment, rate limit or outage) and its open-source fallback spoke this clip.
    - `hash` string, required: Cache key: sha256 of `provider|voice_id|model|format|rate|text` for a legacy voice, or of the `v2|...` form for a voice with a language code (see docs/VOICE-CLIPS.md). On a fallback clip, the FALLBACK clip's key, not the requested voice's.
    - `pending` boolean: `true` when synthesis is still running: come back for it, do not re-synthesise.
    - `text` string, required: The normalised text this clip speaks, echoed so a client can match answers to its own list without relying on order.
    - `url` string, nullable: Where to fetch it. Absent while `pending`, or on failure.
    - `voice` VoiceDescriptor: A voice as the answer names it.
      - `gender` 'female' | 'male', required: A voice's gender. A locale has at most one default voice per gender.
      - `id` string, required: Registry voice id (`amelia`, `fr-fr-1`, `en-gb-m`).
      - `model` string, required: Synthesis model (`eleven_v3`, `kokoro-82m@<rev>`).
      - `provider` string, required: Synthesis provider (`elevenlabs`, `kokoro`, `supertonic`).
  - `voice` VoiceDescriptor, required: A voice as the answer names it.
    - `gender` 'female' | 'male', required: A voice's gender. A locale has at most one default voice per gender.
    - `id` string, required: Registry voice id (`amelia`, `fr-fr-1`, `en-gb-m`).
    - `model` string, required: Synthesis model (`eleven_v3`, `kokoro-82m@<rev>`).
    - `provider` string, required: Synthesis provider (`elevenlabs`, `kokoro`, `supertonic`).

## Other responses

- `400`: Invalid request, or `unknown_voice`: no such voice, locale or gender
- `401`: Missing or invalid API key
- `409`: `voice_not_ready`: the voice exists but cannot synthesise here: no provider voice chosen yet, or an open-source voice on a gateway without the open-source voice service
- `422`: `not_guidance_sentence`: no utterance in the request is a guidance sentence the MapMap apps produce (a request with some is answered 200, with a per-clip `error` on the rest)
- `429`: Quota or rate limit exceeded
- `501`: Voice clips are not enabled (SN_VOICE_DIR unset, or neither SN_ELEVENLABS_API_KEY nor SN_TTS_URL set)

---

[API](https://skmtc.dev/mapmap/apis/sn-gateway.md) · [All operations](https://skmtc.dev/mapmap/apis/sn-gateway/llms.txt) · [OpenAPI document](https://skmtc.dev/mapmap/apis/sn-gateway/revisions/2293be8b4c38?raw)
