> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://api.llynx.io/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://api.llynx.io/docs/_mcp/server.

# Generate speech (TTS)

POST https://api.llynx.io/v2/audio/speech
Content-Type: application/json

Turns text into spoken audio.

Send up to 2,000 characters with the language they are written in, optionally choosing a male or female voice, and receive one complete WAV file ready to play or store. Use it for assistant replies, notifications, IVR prompts and read-aloud features.

The generated audio is returned as a single WAV file containing 16-bit PCM samples in one channel, at the sample rate published in the catalog, which is 24 kHz today. Requests are charged by the number of input characters, at 100 characters per credit; the length of the generated audio does not affect the charge. For reference, the duration of the audio is reported in the `X-Llynx-Audio-Duration-Ms` response header.

The response body is the audio itself, not JSON. When testing with curl, add `-o speech.wav` to save it to a file.

Reference: https://api.llynx.io/docs/api-reference/synthesize

## Authentication

- `Authorization` header (bearer token, required) — Your Llynx API key, sent as `Authorization: Bearer <key>`.

## Request

### Body (application/json)

This endpoint expects a SpeechInput.

- `input` (string, required) — The text to speak, up to 2,000 characters including spaces and punctuation. Whitespace-only text is rejected.
- `language` (string, required) — Language the text is written in, as a tag from the Languages page, for example `hi` or `en`.
- `response_format` (enum, required) — Audio container. Only `wav` is available.
  - Allowed values: `wav`
- `gender` (enum, optional, default: male) — Voice gender for the generated audio.
  - Allowed values: `male`, `female`

## Response

### 200

Complete WAV audio; the credit reserved for the request is reported in headers

- File download.

## Errors

### 400 Bad Request Error

A field is missing or malformed

- `error` (ErrorError, required)

### 401 Unauthorized Error

Missing or invalid API key

- `error` (ErrorError, required)

### 402 Payment Required Error

Not enough credit for this service; buy a pack

- `error` (ErrorError, required)

### 403 Forbidden Error

This key or account is not allowed to make the request, or the account is on a billing hold

- `error` (ErrorError, required)

### 413 Content Too Large Error

Request body too large

- `error` (ErrorError, required)

### 415 Unsupported Media Type Error

Unsupported content type; send application/json

- `error` (ErrorError, required)

### 422 Unprocessable Entity Error

The input could not be processed, for example an unsupported language

- `error` (ErrorError, required)

### 429 Too Many Requests Error

Capacity limit reached; retry later when `retryable` is true

- `error` (ErrorError, required)

### 503 Service Unavailable Error

Temporarily unavailable; a request that fails after admission is refunded

- `error` (ErrorError, required)

## Types

### ErrorError

- `code` (string, required) — Stable machine-readable code.
- `message` (string, required) — Readable explanation, safe to log.
- `request_id` (string, required) — Quote this when you contact us about the request.
- `retryable` (boolean, required) — True only when an immediate retry is safe.

## Examples

**Request**

```json
{
  "input": "Your order has shipped and will arrive on Thursday.",
  "language": "en",
  "response_format": "wav",
  "gender": "female"
}
```

**SDK Code**

```python Python
import os
import requests

response = requests.post(
    "https://api.llynx.io/v2/audio/speech",
    headers={"Authorization": f"Bearer {os.environ['LLYNX_API_KEY']}"},
    json={
        "input": "Your order has shipped and will arrive on Thursday.",
        "language": "en",
        "gender": "female",
        "response_format": "wav",
    },
)
response.raise_for_status()
with open("speech.wav", "wb") as f:
    f.write(response.content)
print(response.headers["X-Llynx-Credits-Reserved"], "credits")
```

```javascript JavaScript
import { writeFile } from "node:fs/promises";

const response = await fetch("https://api.llynx.io/v2/audio/speech", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LLYNX_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    input: "Your order has shipped and will arrive on Thursday.",
    language: "en",
    gender: "female",
    response_format: "wav",
  }),
});
if (!response.ok) throw new Error(await response.text());
await writeFile("speech.wav", Buffer.from(await response.arrayBuffer()));
console.log(response.headers.get("X-Llynx-Credits-Reserved"), "credits");
```

```go Go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
)

func main() {
	payload, _ := json.Marshal(map[string]string{
		"input":           "Your order has shipped and will arrive on Thursday.",
		"language":        "en",
		"gender":          "female",
		"response_format": "wav",
	})
	req, _ := http.NewRequest("POST", "https://api.llynx.io/v2/audio/speech", bytes.NewReader(payload))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("LLYNX_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	audio, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	if resp.StatusCode != http.StatusOK {
		log.Fatalf("%s: %s", resp.Status, audio)
	}
	if err := os.WriteFile("speech.wav", audio, 0o644); err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Header.Get("X-Llynx-Credits-Reserved"), "credits")
}
```

```java Java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class Speak {
    public static void main(String[] args) throws Exception {
        String payload = "{\"input\": \"Your order has shipped and will arrive on Thursday.\","
            + " \"language\": \"en\", \"gender\": \"female\", \"response_format\": \"wav\"}";
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.llynx.io/v2/audio/speech"))
            .header("Authorization", "Bearer " + System.getenv("LLYNX_API_KEY"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .build();

        HttpResponse<byte[]> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofByteArray());
        if (response.statusCode() != 200) {
            throw new RuntimeException(response.statusCode() + " " + new String(response.body(), StandardCharsets.UTF_8));
        }
        Files.write(Path.of("speech.wav"), response.body());
        System.out.println(response.headers().firstValue("X-Llynx-Credits-Reserved").orElse("?") + " credits");
    }
}
```

```swift Swift
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

let key = ProcessInfo.processInfo.environment["LLYNX_API_KEY"] ?? ""
var request = URLRequest(url: URL(string: "https://api.llynx.io/v2/audio/speech")!)
request.httpMethod = "POST"
request.setValue("Bearer \(key)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: [
    "input": "Your order has shipped and will arrive on Thursday.",
    "language": "en",
    "gender": "female",
    "response_format": "wav",
])

let (data, response) = try await URLSession.shared.data(for: request)
let http = response as! HTTPURLResponse
guard http.statusCode == 200 else {
    fatalError("\(http.statusCode): \(String(decoding: data, as: UTF8.self))")
}
try data.write(to: URL(fileURLWithPath: "speech.wav"))
print(http.value(forHTTPHeaderField: "X-Llynx-Credits-Reserved") ?? "?", "credits")
```