# Smallest AI API drop-in

> Smallest AI's own text-to-speech API, REST, SSE and WebSocket, through the cache: keep Smallest AI's SDK, change the base URLs and key.

## Smallest AI's Python SDK

Smallest AI’s text-to-speech API, mirrored under `https://api.ttscacherouter.dev/smallestai`. Point Smallest AI’s SDK at the router and use your router API key as the API key.

**WebSocket**

```python
import os

from smallestai.waves import TTSConfig, WavesStreamingTTS

tts = WavesStreamingTTS(
    TTSConfig(
        voice_id="meher",
        api_key=os.environ["TTS_CACHE_ROUTER_API_KEY"],
        model="lightning_v3.1_pro",
        language="en",
        sample_rate=24000,
    )
)
tts.ws_url = "wss://api.ttscacherouter.dev/smallestai/waves/v1/tts/live"

audio = b"".join(tts.synthesize("Your order has shipped. It arrives on Friday."))
with open("speech.pcm", "wb") as f:
    f.write(audio)
```

**REST**

```python
import os

from smallestai import SmallestAI, SmallestAIEnvironment

client = SmallestAI(
    api_key=os.environ["TTS_CACHE_ROUTER_API_KEY"],
    environment=SmallestAIEnvironment(
        atoms=SmallestAIEnvironment.PRODUCTION.atoms,
        waves="https://api.ttscacherouter.dev/smallestai",
        waves_ws="wss://api.ttscacherouter.dev/smallestai",
        payment=SmallestAIEnvironment.PRODUCTION.payment,
    ),
)

audio = b"".join(
    client.waves.synthesize_tts(
        text="Your order has shipped.",
        voice_id="meher",
        model="lightning_v3.1_pro",
        language="en",
        sample_rate=24000,
        output_format="wav",
    )
)
with open("speech.wav", "wb") as f:
    f.write(audio)
```

The SDK’s `environment` sets the text-to-speech base URLs (`waves` and `waves_ws`); `WavesStreamingTTS` takes its URL in `ws_url`. Requests go to Smallest AI with the Smallest AI key stored under [Provider keys](https://ttscacherouter.dev/dashboard/provider-keys). For a LiveKit agent, see [Smallest AI with LiveKit](https://docs.ttscacherouter.dev/livekit/smallestai.md#provider-plugin).

## WebSocket

`wss://api.ttscacherouter.dev/smallestai/waves/v1/tts/live` speaks Smallest AI’s WebSocket protocol: messages with text and the synthesis settings, answered with `chunk` messages of base64 PCM and `complete`.

Send your router API key in `Authorization: Bearer`. Both of Smallest AI’s ways of streaming text work: `continue` and `flush` (one `complete` after a request’s audio), and continuations with a `context_id`, ended by `continue: false` or `context_close: true` and stopped by `cancel_request: true`. `{"type": "ping"}` gets `{"type": "pong"}`, and a connection with no message for 60 seconds is closed (the `timeout` query parameter sets up to 180), as at Smallest AI.

The router splits the text into sentences and serves each sentence from the cache or synthesizes it at Smallest AI, so the messages are Smallest AI’s with these differences:

- Audio for a sentence starts once the sentence is complete.
- Each sentence is a take of its own: it doesn’t continue from the audio of the sentence before, as text in one Smallest AI continuation does.
- With continuations, a `complete` follows each sentence.
- `session_id` and `request_id` are the router’s own; yours come back as `external_session_id` and `external_request_id`.
- A sentence that fails gets an `error` message for its request, and the rest of that request is dropped.
- `word_timestamps` is accepted, and no word timings are sent. `max_buffer_flush_ms` and `max_buffer_delay_ms` change nothing.

As at Smallest AI, the audio is always 16-bit PCM at `sample_rate`, and text with no letter or digit (`...` alone) gets no audio.

## REST and SSE

- `POST /smallestai/waves/v1/tts`: the audio in the body’s `output_format`, with Smallest AI’s own `Content-Type`.
- `POST /smallestai/waves/v1/tts/live`: Smallest AI’s server-sent events, base64 audio then `done`.
- `GET /smallestai/waves/v1/lightning-v3.1/get_voices` (and `lightning-v3.1-pro`) and `GET /smallestai/waves/v1/voice-cloning` (your cloned voices): Smallest AI’s voice lists, never cached. With the SDK, `client.waves.get_voices(...)` and `client.waves.list_voice_clones()`. [GET /v1/voices](https://docs.ttscacherouter.dev/reference/voices.md) lists both in the router’s own shape.

Any other path under `/smallestai` answers 404.

**REST**

```shell
curl "https://api.ttscacherouter.dev/smallestai/waves/v1/tts" \
  -H "Authorization: Bearer $TTS_CACHE_ROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Your order has shipped.", "voice_id": "meher", "model": "lightning_v3.1_pro", "language": "en", "sample_rate": 24000, "output_format": "wav"}' \
  --output speech.wav
```

**SSE**

```shell
curl -N "https://api.ttscacherouter.dev/smallestai/waves/v1/tts/live" \
  -H "Authorization: Bearer $TTS_CACHE_ROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Your order has shipped.", "voice_id": "meher", "model": "lightning_v3.1_pro", "language": "en", "sample_rate": 24000, "output_format": "pcm"}'
```

## What is cached

- REST and SSE: each request as a whole. The same body again replays the same audio.
- WebSocket: each sentence, so a sentence you have sent before with the same settings comes from the cache whatever came around it. Sentences share the cache with the router’s own [/v1/stream](https://docs.ttscacherouter.dev/clients/stream.md) when the settings are the same.
- A request with `pronunciation_dicts` is cached by the dictionaries’ ids: if you edit a dictionary in place, entries cached before keep the old pronunciation until your cache retention expires them.

## Errors

A missing or wrong router key gets Smallest AI’s own refusal: HTTP 401 `unauthorized`, at the WebSocket handshake too. An org with no Smallest AI key stored gets the same, with a message that links to the dashboard. Errors from Smallest AI, such as a voice of another model or your Smallest AI concurrency limit, reach you as Smallest AI sent them; on the WebSocket, as an `error` message carrying Smallest AI’s message, and the connection stays open.

```http
HTTP/1.1 401 Unauthorized

{"error": "unauthorized"}
```
