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

# OpenAI-compatible API

> Call Finesse text-to-speech with the OpenAI SDKs and any tool that has an OpenAI TTS setting.

Finesse exposes an endpoint that follows OpenAI's `POST /audio/speech` shape. Anything that can talk
to OpenAI text-to-speech (the official SDKs, LiteLLM, Open WebUI, Dify, LibreChat, n8n) can use
Finesse voices: point its OpenAI TTS setting at the Finesse base URL and use your Finesse API key.
Like OpenAI, it returns MP3 when `response_format` is omitted, so tools work with their default
settings.

## Base URL and authentication

```text theme={null}
https://fi.production.flamapis.com/flam-voice-svc/api/v1/openai
```

The SDKs append the endpoint path themselves, so this is the value to use wherever a tool asks for an
OpenAI "base URL". It serves:

| Method | Path | Purpose |
| - | - | - |
| `POST` | `/audio/speech` | Synthesize speech |
| `GET` | `/models` | List models (some tools call it to check a key) |

Authenticate with a [workspace API key](/guides/authentication) (`flv_…`) as a bearer token, which is
what the OpenAI SDKs send:

```text theme={null}
Authorization: Bearer flv_...
```

The `X-API-Key: flv_...` header used by the rest of the Finesse API also works here. Dashboard
session tokens are not accepted. Speech needs a key with the `tts` scope, and counts against your
plan's [rate limits](/guides/rate-limits) like any other synthesis.

## Quick start

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://fi.production.flamapis.com/flam-voice-svc/api/v1/openai/audio/speech" \
    -H "Authorization: Bearer $FINESSE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"model":"finesse-v1","input":"Hello from Finesse.","voice":"charlizie"}' \
    --output speech.mp3
  ```

  ```python Python theme={null}
  import os
  from openai import OpenAI

  client = OpenAI(
      base_url="https://fi.production.flamapis.com/flam-voice-svc/api/v1/openai",
      api_key=os.environ["FINESSE_API_KEY"],
  )

  # The audio is written to the file as it streams in.
  with client.audio.speech.with_streaming_response.create(
      model="finesse-v1",
      voice="charlizie",
      input="Hello from Finesse.",
  ) as response:
      response.stream_to_file("speech.mp3")
  ```

  ```javascript Node theme={null}
  import fs from "node:fs";
  import OpenAI from "openai";

  const client = new OpenAI({
    baseURL: "https://fi.production.flamapis.com/flam-voice-svc/api/v1/openai",
    apiKey: process.env.FINESSE_API_KEY,
  });

  const response = await client.audio.speech.create({
    model: "finesse-v1",
    voice: "charlizie",
    input: "Hello from Finesse.",
    response_format: "mp3",
  });
  fs.writeFileSync("speech.mp3", Buffer.from(await response.arrayBuffer()));
  ```
</CodeGroup>

The response body is the raw audio, not JSON. With no `response_format`, it's MP3
(`audio/mpeg`).

## Request body

<ParamField body="model" type="string" required>
  Any value is accepted; use `finesse-v1`. Values that tools hardcode, such as `tts-1`, are routed to
  the default Finesse model rather than rejected.
</ParamField>

<ParamField body="input" type="string" required>
  The text to synthesize. Maximum **10,000 characters**.
</ParamField>

<ParamField body="voice" type="string" required>
  A voice **alias** (for example `charlizie`) or a voice **id**. See [Choosing a voice](#choosing-a-voice).
</ParamField>

<ParamField body="response_format" type="string">
  `mp3` (the default), `wav` or `pcm`. See [Formats](#formats).
</ParamField>

<ParamField body="language" type="string">
  Finesse extension, not part of the OpenAI API. A language code such as `en` or `hi`. Defaults to the
  voice's primary language; set it for voices that speak several languages. With the Python SDK, pass
  it through `extra_body={"language": "hi"}`.
</ParamField>

<ParamField body="speed" type="number">
  Accepted for compatibility and **ignored**.
</ParamField>

<ParamField body="instructions" type="string">
  Accepted for compatibility and **ignored**.
</ParamField>

## Formats

| `response_format` | What you get | Streamed |
| - | - | - |
| `mp3` (default when omitted) | MP3 audio (`audio/mpeg`) | When your plan includes streaming |
| `wav` | WAV file | When your plan includes streaming |
| `pcm` | Raw 16-bit signed little-endian PCM (`s16le`), 24 kHz, mono, no header. The same layout as OpenAI's `pcm` | When your plan includes streaming |
| `opus`, `aac`, `flac` | Not supported: `400 unsupported_response_format` | - |

<Note>
  On a deployment without an MP3 encoder available, `mp3` (including the omitted default) returns
  `400 unsupported_response_format` with a message asking for `wav` or `pcm`. Pass one of those
  explicitly if you see it.
</Note>

## Streaming

When your plan includes streaming, every supported format (`mp3`, `wav` and `pcm`) is sent as it's
generated, so you can start playback before the whole clip is ready. Without streaming on your plan,
the request still succeeds: the whole clip is synthesized first and returned in one response. Use
`pcm` for real-time playback: it has no header and every chunk is playable samples.

<CodeGroup>
  ```python Python theme={null}
  import os
  import pyaudio
  from openai import OpenAI

  client = OpenAI(
      base_url="https://fi.production.flamapis.com/flam-voice-svc/api/v1/openai",
      api_key=os.environ["FINESSE_API_KEY"],
  )

  player = pyaudio.PyAudio()
  stream = player.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True)

  with client.audio.speech.with_streaming_response.create(
      model="finesse-v1",
      voice="charlizie",
      input="Streaming hello from Finesse.",
      response_format="pcm",
  ) as response:
      # An even chunk size keeps 16-bit samples whole.
      for chunk in response.iter_bytes(chunk_size=4096):
          stream.write(chunk)

  stream.close()
  player.terminate()
  ```

  ```bash cURL (play with SoX) theme={null}
  curl -sN -X POST "https://fi.production.flamapis.com/flam-voice-svc/api/v1/openai/audio/speech" \
    -H "Authorization: Bearer $FINESSE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"model":"finesse-v1","input":"Streaming hello from Finesse.","voice":"charlizie","response_format":"pcm"}' \
    | play -t raw -r 24000 -e signed -b 16 -c 1 -
  ```
</CodeGroup>

## Choosing a voice

`voice` takes either:

* **An alias**: a short, stable name for a public Finesse voice, such as `charlizie`. Aliases are
  lowercase letters, digits and hyphens.
* **A voice id**: the `id` of any voice your workspace can use, including your own cloned voices.

A voice that doesn't exist, or that belongs to another workspace, returns `404`.

To see what's available, call [List voices](/voice-api/list-voices). It returns your workspace's
voices first, then the public platform voices, which carry an `alias` field. The list is paginated
with `page` and `page_size`:

```bash theme={null}
curl "https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/voices?page=1&page_size=50" \
  -H "X-API-Key: $FINESSE_API_KEY"
```

<Note>
  OpenAI voice names such as `alloy` or `nova` are not Finesse voices. Replace them with a Finesse
  alias or voice id in any copied configuration.
</Note>

## Errors

Errors use OpenAI's shape, so OpenAI clients surface `error.message` as-is:

```json theme={null}
{
  "error": {
    "message": "response_format \"opus\" is not supported. Use \"mp3\", \"wav\" or \"pcm\".",
    "type": "invalid_request_error",
    "param": "response_format",
    "code": "unsupported_response_format"
  }
}
```

| Status | `code` | When |
| - | - | - |
| `400` | `invalid_json` | The request body isn't a valid JSON object |
| `400` | `missing_required_parameter` | `input` or `voice` is missing or empty (`param` names which) |
| `400` | `input_too_long` | `input` is over 10,000 characters (`param: input`) |
| `400` | `unsupported_stream_format` | `stream_format` is set to anything other than `audio` (including `sse`). Omit it to get raw audio bytes (`param: stream_format`) |
| `400` | `unsupported_response_format` | `response_format` is `opus`, `aac`, `flac` or another unknown value (or `mp3` on a server without an MP3 encoder) |
| `400` | `invalid_request` | Other invalid requests, such as a voice that isn't `ready` yet |
| `401` | `invalid_api_key` | The key is missing, revoked or wrong |
| `401` | `key_expired` | The key has expired. Rotate it or create a new one |
| `403` | `insufficient_scope` | The key lacks the `tts` scope (`/audio/speech` only) |
| `404` | `voice_not_found` | The `voice` alias or id doesn't exist or isn't available to your workspace (`param: voice`) |
| `422` | `invalid_input` | The synthesis engine could not use the `input`. Change it rather than retrying |
| `429` | `insufficient_quota` | Your workspace is out of credits |
| `429` | `rate_limit_exceeded` | Over your plan's [requests-per-second or concurrent-synthesis limit](/guides/rate-limits) (`type: rate_limit_error`). Wait `Retry-After`, then retry with backoff |
| `502` | `server_error` | The synthesis engine is unavailable or warming up (`type: server_error`). Retry with backoff |
| `503` | `server_error` | The synthesis engine is at capacity. Wait `Retry-After`, then retry with backoff |
| `503` | `encoder_busy` | All MP3 encoders are busy (`type: server_error`). Retry with backoff, or use `wav` / `pcm` |

The `message` text above is illustrative; branch on `status` and `code`, not on the message.

## Use with tools

Every tool below needs the same four values:

| Setting | Value |
| - | - |
| Base URL | `https://fi.production.flamapis.com/flam-voice-svc/api/v1/openai` |
| API key | Your `flv_…` key |
| Model | `finesse-v1` |
| Voice | A Finesse alias or voice id, for example `charlizie` |

Field names differ between tools and versions, so match them by meaning. The default response
format (`mp3`) works, so you can leave a tool's format setting as it is.

<AccordionGroup>
  <Accordion title="Open WebUI">
    In Open WebUI's admin audio settings, set the text-to-speech engine to **OpenAI**, then fill in the
    OpenAI API base URL, API key, TTS model and TTS voice with the values in the table above.
  </Accordion>

  <Accordion title="LiteLLM">
    Add Finesse to your proxy config as an `openai/` model with `api_base` set to the Finesse base URL:

    ```yaml config.yaml theme={null}
    model_list:
      - model_name: finesse-tts
        litellm_params:
          model: openai/finesse-v1
          api_base: https://fi.production.flamapis.com/flam-voice-svc/api/v1/openai
          api_key: os.environ/FINESSE_API_KEY
        model_info:
          mode: audio_speech
    ```

    Then call the proxy's `/audio/speech` endpoint with `model: "finesse-tts"` and a Finesse `voice`.
  </Accordion>

  <Accordion title="Dify">
    Add a model provider that speaks the OpenAI API with a custom endpoint (for example the
    OpenAI-API-compatible provider), and add a text-to-speech model to it: model name `finesse-v1`,
    the Finesse base URL as the endpoint, your API key, and a Finesse alias as the voice. Then select
    that model as the workspace's or app's text-to-speech model.
  </Accordion>

  <Accordion title="LibreChat">
    Configure the OpenAI text-to-speech provider in the `speech` section of `librechat.yaml`:

    ```yaml librechat.yaml theme={null}
    speech:
      tts:
        openai:
          url: "https://fi.production.flamapis.com/flam-voice-svc/api/v1/openai"
          apiKey: "${FINESSE_API_KEY}"
          model: "finesse-v1"
          voices: ["charlizie"]
    ```

    Check LibreChat's speech documentation for your version to confirm whether `url` expects the base
    URL or the full `/audio/speech` endpoint.
  </Accordion>

  <Accordion title="n8n">
    Create an OpenAI credential with your Finesse API key and set its base URL to the Finesse base URL.
    In the OpenAI node, use the audio "generate" operation with model `finesse-v1` and a Finesse voice.
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.