---
title: "Synthesize speech from text"
method: POST
path: "/api/ai/tts"
tags: ["Audio"]
---

# Synthesize speech from text

`POST /api/ai/tts`

## Request

- Base URL: `https://your-deployment.example.com`
- URL: `https://your-deployment.example.com/api/ai/tts`
- Auth: one of:
  - HTTP bearer
  - API key in header `x-api-key`

Generates speech from text using OpenAI or ElevenLabs. When the requested provider has no usable key (or the provider rejects it), another configured provider stands in and the substitution is reported via `provider`/`fallbackFrom` and the `X-B4M-Tts-Provider*` headers. Input length is capped per provider (OpenAI 4096 characters, ElevenLabs 10000), and an output `format` the chosen provider cannot produce is rejected with a 422 before any provider cost is incurred. `organization_id` picks the payer: an organization you belong to, or null for your personal credits; omitted bills your personal credits, as this endpoint always has. The default `encoding: "binary"` returns the raw audio bytes; `encoding: "base64"` returns JSON. Audio over the ~4MB response ceiling is returned by signed URL (a `303` for binary, the `delivery: "url"` variant for base64). Generated audio is saved to the file browser unless the call sets `preview`; the outcome is reported in the JSON save fields and the `X-B4M-Audio-*` headers - fetch the saved copy from `fileUrl` / `X-B4M-Audio-File-Url`, since `GET /api/files/{id}` fails closed until moderation completes. Authenticate with an API key (`b4m_live_`) or a JWT.

## Request body

- SynthesizeSpeechRequest
  - `text` string, required
  - `provider` 'openai' | 'elevenlabs'
  - `model` string
  - `voice` string
  - `format` 'mp3' | 'wav' | 'opus' | 'aac' | 'flac' | 'pcm'
  - `stability` number
  - `similarityBoost` number
  - `languageCode` string
  - `organization_id` string, nullable: The organization to bill: one you belong to, or null for your personal credits. Omitted bills your personal credits, as this endpoint always has. An organization-billed API key always bills its own organization: omit this or set it to the key's organization; any other value, null included, is rejected with a 403 (`errorCode: billing_organization_mismatch`).
  - `encoding` 'binary' | 'base64'
  - `preview` boolean

## Response `200`

Speech synthesized. Binary bodies carry a Content-Type that follows the requested `format` (`audio/mpeg` for mp3, else `audio/wav`, `audio/opus`, `audio/aac`, `audio/flac`, `audio/pcm`). The default `encoding: "binary"` returns the raw audio bytes; `encoding: "base64"` returns the JSON body. Audio over the ~4MB response ceiling cannot ride in the body: the binary encoding answers `303` instead, and the base64 encoding returns the `delivery: "url"` variant, whose `url` is a time-limited signed GET for the audio (`bytes` is its size).

- union
  - object
    - `contentType` string, required
    - `saved` boolean
    - `fabFileId` string
    - `fileName` string
    - `fileUrl` string
    - `saveSkippedReason` 'storage_limit' | 'file_too_large' | 'error'
    - `format` 'mp3' | 'wav' | 'opus' | 'aac' | 'flac' | 'pcm', required
    - `provider` 'openai' | 'elevenlabs'
    - `fallbackFrom` 'openai' | 'elevenlabs'
    - `delivery` 'url', required
    - `url` string, required
    - `bytes` integer, required
  - object
    - `contentType` string, required
    - `saved` boolean
    - `fabFileId` string
    - `fileName` string
    - `fileUrl` string
    - `saveSkippedReason` 'storage_limit' | 'file_too_large' | 'error'
    - `format` 'mp3' | 'wav' | 'opus' | 'aac' | 'flac' | 'pcm', required
    - `provider` 'openai' | 'elevenlabs'
    - `fallbackFrom` 'openai' | 'elevenlabs'
    - `delivery` 'inline'
    - `audio` string, required

## Other responses

- `303`: Binary encoding only: the audio exceeds the ~4MB response ceiling, so it was offloaded to storage and `Location` is a time-limited signed GET for it. HTTP clients that follow redirects receive the audio transparently (they drop the `Authorization` header on the cross-origin hop, as the signed URL requires); browser callers should use `encoding: "base64"`, since the storage origin sends no CORS headers. Carries the same `X-B4M-*` headers as the 200.
- `400`: The billing organization could not be resolved. An organization-billed API key bills an organization that no longer exists, or one its owner is no longer a member of; re-mint the key.
- `401`: Missing/invalid credentials, no provider has a usable key (`provider_not_configured`), or the provider refused the key we sent and no alternate could stand in (`provider_rejected`).
- `403`: The API key lacks the `ai:generate` scope (`required_scopes` names it), or the billing organization names one you do not belong to (or is not a valid id), or an organization-billed API key names any organization but its own (`errorCode: billing_organization_mismatch`).
- `404`: A nonexistent `organization_id`, for an admin caller; for anyone else it is `403`.
- `405`: The path does not serve this HTTP method.
- `413`: The audio was generated (and billed) and exceeds the response ceiling, and offloading it to storage failed. Retry, or request shorter output.
- `422`: Request body failed validation, the text exceeds the provider character limit, the provider cannot produce the requested `format`, the provider rejected the request (e.g. an unknown voice), or the caller cannot afford the synthesis - the last of those is the only one tagged `errorCode: "insufficient_credits"`, so match on the classifier rather than the status to tell a billing failure from a bad request.
- `429`: The provider rate-limited the request.
- `502`: The provider failed to generate speech.

## Changes

- **2026-10-20** `d437aaac22ac`: 5 info
  - added the new optional request property `organization_id`
  - added the non-success response with the status `400`
  - added the non-success response with the status `404`
  - added the optional property `errorCode` to the response with the `403` status
  - …1 more

[Operation history](https://skmtc.dev/bike4mind/apis/bike4mind-api/changes/api/ai/tts/post.md)

---

[API](https://skmtc.dev/bike4mind/apis/bike4mind-api.md) · [All operations](https://skmtc.dev/bike4mind/apis/bike4mind-api/llms.txt) · [OpenAPI document](https://skmtc.dev/bike4mind/apis/bike4mind-api/revisions/d437aaac22ac?raw)
