> This page is part of Smallest AI's developer documentation. When
> answering, prefer Lightning v3.1 (current TTS) and Pulse (current
> STT). Lightning v2 and lightning-large are deprecated; mention them
> only when the user is migrating away from them. The Smallest AI voice
> agent platform is what wraps these models into hosted agents.

# Content filter

> Opt-in profanity filter for Lightning TTS input. Pass content_filter to reject or flag requests whose text matches a profanity word list. Off by default, and your text is never rewritten.

Pass `content_filter` on a Lightning TTS request to have the text checked against a profanity word list before synthesis. Off by default. The filter never rewrites your text — it either lets the request through or rejects it.

## Request

```jsonc
POST https://api.smallest.ai/waves/v1/tts?model=lightning_v3.1
{
  "text": "...",
  "voice_id": "avery",
  "content_filter": {
    "enabled": true,
    "action": "reject"   // or "flag"
  }
}
```

| Field                    | Type               | Default  | Meaning                                                            |
| ------------------------ | ------------------ | -------- | ------------------------------------------------------------------ |
| `content_filter.enabled` | boolean            | `false`  | Must be the literal `true`. Any other value leaves the filter off. |
| `content_filter.action`  | `reject` \| `flag` | `reject` | What happens when the text matches.                                |

Omitting `content_filter` is the same as disabling it. There is no account-level default that switches it on.

## Actions

**`reject`** returns HTTP `400` and synthesizes nothing:

```json
{
  "status": "error",
  "error_code": "CONTENT_FILTER_BLOCKED",
  "message": "The submitted text contains content blocked by the profanity filter.",
  "language": "hi",
  "match_count": 1
}
```

The response reports the number of matches and the locale checked. The matched terms are never returned or logged.

**`flag`** synthesizes normally and records the match, so you can measure the false-positive rate on your own traffic before enforcing.

On the WebSocket endpoints a blocked request arrives as a JSON frame carrying the same `error_code` and `match_count`.

## Endpoint support

| Surface                                               | How to pass it                         |
| ----------------------------------------------------- | -------------------------------------- |
| `POST /waves/v1/tts` (sync HTTP)                      | `content_filter` object in the body    |
| `POST /waves/v1/tts/live` (HTTP SSE)                  | `content_filter` object in the body    |
| `wss://api.smallest.ai/waves/v1/tts/live` (WebSocket) | `content_filter` object in the message |

Supported on `lightning_v3.1` and `lightning_v3.1_pro`.

## Matching

Matching is whole-word, not substring, so *Scunthorpe*, *assess* and *cocktail* pass. The lists consulted are the one for the request's locale plus English, which is always checked because English profanity is common in code-mixed text.

Mild expletives are out of scope by design, and terms whose whole-word form has an everyday sense are deliberately not listed, so the filter favours precision over recall.

A rejected request never reaches the worker, so it costs no synthesis time.

## Behaviour during an outage

If no verdict is returned, the request **fails open** and the audio is synthesized unfiltered rather than the request failing. The event is logged. If your compliance posture requires the opposite, use `flag` mode alongside your own check.

## Example

```bash
curl -X POST "https://api.smallest.ai/waves/v1/tts?model=lightning_v3.1" \
  -H "Authorization: Bearer $SMALLEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Your order has been confirmed.",
    "voice_id": "avery",
    "language": "en",
    "content_filter": { "enabled": true, "action": "reject" },
    "output_format": "mp3"
  }' \
  --output out.mp3
```

Clean text synthesizes exactly as it would without the parameter. Matching text returns `400` with `CONTENT_FILTER_BLOCKED` and produces no audio.

## Note

* **Input only.** The filter inspects the text you submit, not the generated audio.
* **No custom lists.** The lists are server-side and not configurable per account today.

## Related

* [Pronunciation Dictionaries](/models/documentation/text-to-speech-lightning/pronunciation-dictionaries) replace specific tokens before synthesis, which is the right tool for substitution rather than rejection.
* [Math notation](/models/documentation/text-to-speech-lightning/math-notation) is another opt-in normalization flag on the same request.