Appearance
For clean Markdown of any page, append .md to the page URL. For a complete documentation index, see For full documentation content, see For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at
Filter profanity
For the complete documentation index, see llms.txt
cURL quickstart, Streaming profanity filter
bash
# Connect to the streaming endpoint with filter_profanity enabled.
# Send 16 kHz mono PCM audio frames as binary WebSocket messages.
wscat \
--header "Authorization: YOUR_API_KEY" \
--connect "wss://streaming.assemblyai.com/v3/ws?sample_rate=16000&speech_model=u3-rt-pro&format_turns=true&filter_profanity=true"Profane words in transcript (in both partial and final turns) are replaced with asterisks. The mask preserves length, apostrophes, and punctuation.
Overview
Streaming profanity filtering lets you automatically mask profane words in your streaming transcripts in real time. When enabled, the API replaces profane words with asterisks in both partial and final turns before sending them to the client.
The mask uses the first letter of the word followed by n - 1 asterisks (for example, shit becomes s***). Apostrophes, capitalization, and surrounding punctuation are preserved (for example, shit's becomes s***'s).
Profanity filtering supports all streaming models: u3-rt-pro, universal-streaming-english, and universal-streaming-multilingual. It also works alongside other features such as format_turns and PII redaction.
For profanity filtering on pre-recorded audio, see Filter profanity from transcripts.
Connection parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
filter_profanity | boolean | No | false | Enable real-time profanity filtering. When true, profane words in both partial and final turns are masked with asterisks (first letter preserved). The server accepts the truthy strings true, 1, and yes. Invalid values cause the WebSocket to close with code 3006. |
include_partial_turns | boolean | No | true | When false, the API only sends final turns. Useful with filter_profanity: true if you display partials directly to end-users and want to avoid any unmasked profanity flashing during word completion. |
Quickstart
Get started with streaming profanity filtering using the code below. This example streams 16 kHz mono PCM audio from your microphone and prints each turn with profanity masked.
Install the required libraries
bash
pip install websocket-client pyaudioCreate a new file main.py and paste the code below. Replace `` with your API key.
Run with python main.py and speak into your microphone.
python
import pyaudio
import websocket
import json
import threading
import time
from urllib.parse import urlencode
YOUR_API_KEY = "<YOUR_API_KEY>"
CONNECTION_PARAMS = {
"sample_rate": 16000,
"speech_model": "u3-rt-pro",
"format_turns": "true",
"filter_profanity": "true",
}
API_ENDPOINT_BASE_URL = "wss://streaming.assemblyai.com/v3/ws"
API_ENDPOINT = f"{API_ENDPOINT_BASE_URL}?{urlencode(CONNECTION_PARAMS)}"
FRAMES_PER_BUFFER = 800
SAMPLE_RATE = CONNECTION_PARAMS["sample_rate"]
CHANNELS = 1
FORMAT = pyaudio.paInt16
audio = None
stream = None
ws_app = None
audio_thread = None
stop_event = threading.Event()
def on_open(ws):
print("WebSocket connection opened.")
def stream_audio():
global stream
while not stop_event.is_set():
try:
audio_data = stream.read(FRAMES_PER_BUFFER, exception_on_overflow=False)
ws.send(audio_data, websocket.ABNF.OPCODE_BINARY)
except Exception as e:
print(f"Error streaming audio: {e}")
break
global audio_thread
audio_thread = threading.Thread(target=stream_audio)
audio_thread.daemon = True
audio_thread.start()
def on_message(ws, message):
try:
data = json.loads(message)
msg_type = data.get("type")
if msg_type == "Begin":
print(f"Session began: ID={data.get('id')}")
elif msg_type == "Turn":
transcript = data.get("transcript", "")
end_of_turn = data.get("end_of_turn", False)
if end_of_turn:
print(f"\r{' ' * 80}\r{transcript}")
elif msg_type == "Termination":
print(f"\nSession terminated: {data.get('audio_duration_seconds', 0)}s of audio")
except Exception as e:
print(f"Error handling message: {e}")
def on_error(ws, error):
print(f"\nWebSocket Error: {error}")
stop_event.set()
def on_close(ws, close_status_code, close_msg):
print(f"\nWebSocket Disconnected: Status={close_status_code}")
global stream, audio
stop_event.set()
if stream:
if stream.is_active():
stream.stop_stream()
stream.close()
if audio:
audio.terminate()
def run():
global audio, stream, ws_app
audio = pyaudio.PyAudio()
stream = audio.open(
input=True,
frames_per_buffer=FRAMES_PER_BUFFER,
channels=CHANNELS,
format=FORMAT,
rate=SAMPLE_RATE,
)
print("Speak into your microphone. Press Ctrl+C to stop.")
ws_app = websocket.WebSocketApp(
API_ENDPOINT,
header={"Authorization": YOUR_API_KEY},
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close,
)
ws_thread = threading.Thread(target=ws_app.run_forever)
ws_thread.daemon = True
ws_thread.start()
try:
while ws_thread.is_alive():
time.sleep(0.1)
except KeyboardInterrupt:
print("\nStopping...")
stop_event.set()
if ws_app and ws_app.sock and ws_app.sock.connected:
ws_app.send(json.dumps({"type": "Terminate"}))
time.sleep(2)
if ws_app:
ws_app.close()
ws_thread.join(timeout=2.0)
if __name__ == "__main__":
run()Install the required libraries
bash
pip install "assemblyai>=0.54.0" pyaudioCreate a new file main.py and paste the code below. Replace `` with your API key.
Run with python main.py and speak into your microphone.
python
import logging
from typing import Type
import assemblyai as aai
from assemblyai.streaming.v3 import (
BeginEvent,
StreamingClient,
StreamingClientOptions,
StreamingError,
StreamingEvents,
StreamingParameters,
TurnEvent,
TerminationEvent,
)
api_key = "<YOUR_API_KEY>"
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def on_begin(self: Type[StreamingClient], event: BeginEvent):
print(f"Session started: {event.id}")
def on_turn(self: Type[StreamingClient], event: TurnEvent):
print(f"{event.transcript} (end_of_turn={event.end_of_turn})")
def on_terminated(self: Type[StreamingClient], event: TerminationEvent):
print(
f"Session terminated: {event.audio_duration_seconds} seconds of audio processed"
)
def on_error(self: Type[StreamingClient], error: StreamingError):
print(f"Error occurred: {error}")
def main():
client = StreamingClient(
StreamingClientOptions(
api_key=api_key,
api_host="streaming.assemblyai.com",
)
)
client.on(StreamingEvents.Begin, on_begin)
client.on(StreamingEvents.Turn, on_turn)
client.on(StreamingEvents.Termination, on_terminated)
client.on(StreamingEvents.Error, on_error)
client.connect(
StreamingParameters(
sample_rate=16000,
speech_model="u3-rt-pro",
format_turns=True,
filter_profanity=True,
)
)
try:
client.stream(
aai.extras.MicrophoneStream(sample_rate=16000)
)
finally:
client.disconnect(terminate=True)
if __name__ == "__main__":
main()Install the required libraries
bash
npm install ws node-record-lpcm16Create a new file index.mjs and paste the code below. Replace `` with your API key.
Run with node index.mjs and speak into your microphone.
javascript
import WebSocket from "ws";
import recorder from "node-record-lpcm16";
const YOUR_API_KEY = "<YOUR_API_KEY>";
const SAMPLE_RATE = 16000;
const params = new URLSearchParams({
sample_rate: String(SAMPLE_RATE),
speech_model: "u3-rt-pro",
format_turns: "true",
filter_profanity: "true",
});
const url = `wss://streaming.assemblyai.com/v3/ws?${params}`;
const ws = new WebSocket(url, {
headers: { Authorization: YOUR_API_KEY },
});
let recording;
ws.on("open", () => {
console.log("Connected to AssemblyAI Streaming API");
recording = recorder.record({
channels: 1,
sampleRate: SAMPLE_RATE,
audioType: "raw",
});
recording.stream().on("data", (chunk) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(chunk);
}
});
});
ws.on("message", (data) => {
const msg = JSON.parse(data);
if (msg.type === "Turn" && msg.end_of_turn) {
console.log(msg.transcript);
}
});
ws.on("error", (err) => console.error("WebSocket error:", err));
ws.on("close", () => console.log("Disconnected"));
process.on("SIGINT", () => {
if (recording) recording.stop();
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "Terminate" }));
}
setTimeout(() => {
ws.close();
process.exit(0);
}, 500);
});Install the required libraries
bash
npm install assemblyai node-record-lpcm16Create a new file index.mjs and paste the code below. Replace `` with your API key.
Run with node index.mjs and speak into your microphone.
javascript
import { AssemblyAI } from "assemblyai";
import recorder from "node-record-lpcm16";
const apiKey = "<YOUR_API_KEY>";
const SAMPLE_RATE = 16000;
const client = new AssemblyAI({ apiKey });
const transcriber = client.streaming.transcriber({
sampleRate: SAMPLE_RATE,
speechModel: "u3-rt-pro",
formatTurns: true,
filterProfanity: true,
});
transcriber.on("open", ({ id }) => {
console.log(`Session started: ${id}`);
});
transcriber.on("turn", (turn) => {
console.log(`${turn.transcript} (end_of_turn=${turn.end_of_turn})`);
});
transcriber.on("close", (code, reason) => {
console.log(`Session terminated: ${code} ${reason}`);
});
transcriber.on("error", (error) => {
console.error(`Error occurred: ${error}`);
});
async function main() {
await transcriber.connect();
console.log("Speak into your microphone. Press Ctrl+C to stop.");
const recording = recorder.record({
channels: 1,
sampleRate: SAMPLE_RATE,
audioType: "raw",
});
recording.stream().on("data", (chunk) => transcriber.sendAudio(chunk));
process.on("SIGINT", async () => {
recording.stop();
await transcriber.close(true);
process.exit(0);
});
}
main();Profanity filtering applies to both partial and final turns, but during word-completion an unmasked partial can briefly appear before the model resolves the word and applies the mask. If your application surfaces partials directly to end-users (for example a live caption stream or voice-agent UI), set include_partial_turns: false on the connection to suppress all partial turns and only receive masked finals. The default is true (partials enabled), so this requires an explicit opt-out.
Example output
With filter_profanity=true, a final turn might look like:
plain
s*** is what you say when you stub your toe.The mask preserves word length, apostrophes, and surrounding punctuation, so a word like shit's is returned as s***'s and motherfucker becomes m***********.
Supported models
Streaming profanity filtering works with all streaming models on both the US and EU endpoints:
u3-rt-prouniversal-streaming-englishuniversal-streaming-multilingual
Troubleshooting
The streaming filter targets the same word list as pre-recorded profanity filtering and only masks words on that list. Some words you might consider profane, such as crap and damn, are intentionally not masked and pass through unchanged. If you need stricter filtering, apply your own post-processing on top of the masked transcript.
Profanity masking applies during word classification, so an unmasked partial can briefly appear before the word is fully recognized and masked. If your UI surfaces partials directly to users, set include_partial_turns: false on the connection. Final turns are always masked.
{/* TODO: Pricing section pending billing PR #16135 merge. cc @lee.vaughn @support-team - add $0.01/hr line + cross-link to /docs/pricing once Adam Urpsis's billing PR lands and Mary Pat confirms billing is live in production. */}