TL;DR – Quick Integration Overview API Platform: Pulse STT by Smallest AI – a state-of-the-art speech-to-text API supporting real-time streaming and batch audio transcription.
Key Features:
Transcribes in 32+ languages with automatic language detection
Ultra-low latency: ~64ms time-to-first-transcript for streaming
Rich metadata: word timestamps, speaker diarization, emotion detection, age/gender estimation, PII redaction
Integration Methods:
Pre-Recorded Audio: POST https://waves-api.smallest.ai/api/v1/pulse/get_text – upload files for batch processing
Real-Time Streaming: wss://waves-api.smallest.ai/api/v1/pulse/get_text – WebSocket for live transcription
Developer Experience: Use any HTTP/WebSocket client or official SDKs (Python, Node.js). Authentication via a single API key.
Why Pulse STT? Compared to other providers, Pulse offers faster response (64ms vs 200-500ms for typical cloud STT) and all-in-one features (no need for separate services for speaker ID, sentiment, or PII masking).
Quick Links:
API Console – Get your API key
Documentation – Full API reference
Python SDK – Official client
Introduction: Why Voice Integration Matters Voice is becoming the next frontier for user interaction. From virtual assistants and voice bots to real-time transcription in meetings, speech interfaces are making software more accessible and user-friendly. Developers today have access to Automatic Speech Recognition (ASR) APIs that convert voice to text, opening up possibilities for hands-free control, live captions, voice search, and more.
However, integrating voice AI is more than just getting raw text from audio. Modern use cases demand speed and accuracy – a voice assistant needs to transcribe commands almost instantly, and a call center analytics tool might need not just the transcript but also who spoke when and how they said it.
Latency is critical. A delay of even a second feels laggy in conversation. Traditional cloud speech APIs often have 500–1200ms latency for live transcription, with better ones hovering around 200–250ms. This has pushed the industry toward ultra-low latency – under 300ms – to enable seamless real-time interactions.
In this guide, we'll walk through how to integrate an AI voice & speech API that meets these modern demands using Smallest AI's Pulse STT . By the end, you'll know how to:
Transcribe audio files (WAV/MP3) to text using a simple HTTP API
Stream live audio for instantaneous transcripts via WebSockets
Leverage advanced features like timestamps, speaker diarization, and emotion detection
Use both Python and Node.js to integrate voice capabilities
Understanding Pulse STT Pulse is the speech-to-text/ ASR(automatic speech recognition) model from Smallest AI's "Waves" platform. It's designed for fast, accurate, and rich transcription with industry-leading latency – around 64 milliseconds to first transcribed word TTFT for streaming audio. This is an order of magnitude faster than many alternatives.
Highlight Features Feature
Description
Real-Time & Batch Modes
Stream live audio via WebSocket or upload files via HTTP POST
32+ Languages
English, Spanish, Hindi, French, German, Arabic, Japanese, and more with auto-detection
Word/Sentence Timestamps
Know exactly when each word was spoken (great for subtitles)
Speaker Diarization
Differentiate speakers: "Speaker A said X, Speaker B said Y"
Emotion Detection
Tag segments with emotions: happy, angry, neutral, etc.
Age/Gender Estimation
Infer speaker demographics for analytics
PII/PCI Redaction
Automatically mask credit cards, SSNs, and personal info
64ms Latency
Time-to-first-transcript in streaming mode
Getting Started: Authentication Step 1: Get Your API Key Sign up on the Smallest AI Console and generate an API key. This key authenticates all your requests.
Step 2: Test Your Key curl -H "Authorization: Bearer $SMALLEST_API_KEY" \
https:
curl -H "Authorization: Bearer $SMALLEST_API_KEY" \
https:
curl -H "Authorization: Bearer $SMALLEST_API_KEY" \
https:
Authentication Header All requests require this header:
Authorization: Bearer < YOUR_API_KEY >
Authorization: Bearer < YOUR_API_KEY >
Authorization: Bearer < YOUR_API_KEY >
Part 1: Transcribing Audio Files (REST API) The Pre-Recorded API is perfect for batch processing voicemails, podcasts, meeting recordings, or any existing audio files.
Endpoint
Query Parameters Parameter
Type
Description
model
string
Model identifier: pulse (required)
language
string
ISO code (en, es, hi) or multi for auto-detect
word_timestamps
boolean
Include word-level timing data
diarize
boolean
Enable speaker diarization
emotion_detection
boolean
Detect speaker emotions
age_detection
boolean
Estimate speaker age group
gender_detection
boolean
Estimate speaker gender
Supported Languages (32+) Italian, Spanish, English, Portuguese, Hindi, German, French, Ukrainian, Russian, Kannada, Malayalam, Polish, Marathi, Gujarati, Czech, Slovak, Telugu, Odia, Dutch, Bengali, Latvian, Estonian, Romanian, Punjabi, Finnish, Swedish, Bulgarian, Tamil, Hungarian, Danish, Lithuanian, Maltese, and auto-detection (multi).
cURL Example
curl --request POST \
--url "https://waves-api.smallest.ai/api/v1/pulse/get_text?model=pulse&language=en&diarize=true&word_timestamps=true&emotion_detection=true" \
--header "Authorization: Bearer $SMALLEST_API_KEY" \
--header "Content-Type: audio/wav" \
--data -binary "@/path/to/audio.wav"
curl --request POST \
--url "https://waves-api.smallest.ai/api/v1/pulse/get_text?model=pulse&language=en&diarize=true&word_timestamps=true&emotion_detection=true" \
--header "Authorization: Bearer $SMALLEST_API_KEY" \
--header "Content-Type: audio/wav" \
--data -binary "@/path/to/audio.wav"
curl --request POST \
--url "https://waves-api.smallest.ai/api/v1/pulse/get_text?model=pulse&language=en&diarize=true&word_timestamps=true&emotion_detection=true" \
--header "Authorization: Bearer $SMALLEST_API_KEY" \
--header "Content-Type: audio/wav" \
--data -binary "@/path/to/audio.wav"
Python Example
import os
import requests
< p > API_KEY = os.getenv("SMALLEST_API_KEY")< br > audio_file = "meeting_recording.wav"</ p >
< p > url = "< a href ="https://waves-api.smallest.ai/api/v1/pulse/get_text" data-framer-link ="Link:{" url ":"https://waves-api.smallest.ai/api/v1/pulse/get_text","type":"url"}">https://waves-api.smallest.ai/api/v1/pulse/get_text</ a > "< br > params = { < br > "model": "pulse",< br > "language": "en",< br > "word_timestamps": "true",< br > "diarize": "true",< br > "emotion_detection": "true"< br > }< br > headers = { < br > "Authorization": f"Bearer { API_KEY } ",< br > "Content-Type": "audio/wav"< br > }</ p >
< p > with open(audio_file, "rb") as f:< br > audio_data = f.read()</ p >
< p > response = requests.post(url, params=params, headers=headers, data=audio_data)< br > result = response.json()</ p >
< h1 > Print transcription</ h1 >
< p > print("Transcription:", result.get("transcription"))</ p >
< h1 > Print word-level details with speaker info</ h1 >
< p > for word in result.get("words", []):< br > speaker = word.get("speaker", "N/A")< br > print(f" [Speaker { speaker } ] [{ word [ 'start' ] :.2f}s - { word [ 'end' ] :.2f}s] { word [ 'word' ] } ")</ p >
< h1 > Check emotions</ h1 >
import os
import requests
< p > API_KEY = os.getenv("SMALLEST_API_KEY")< br > audio_file = "meeting_recording.wav"</ p >
< p > url = "< a href ="https://waves-api.smallest.ai/api/v1/pulse/get_text" data-framer-link ="Link:{" url ":"https://waves-api.smallest.ai/api/v1/pulse/get_text","type":"url"}">https://waves-api.smallest.ai/api/v1/pulse/get_text</ a > "< br > params = { < br > "model": "pulse",< br > "language": "en",< br > "word_timestamps": "true",< br > "diarize": "true",< br > "emotion_detection": "true"< br > }< br > headers = { < br > "Authorization": f"Bearer { API_KEY } ",< br > "Content-Type": "audio/wav"< br > }</ p >
< p > with open(audio_file, "rb") as f:< br > audio_data = f.read()</ p >
< p > response = requests.post(url, params=params, headers=headers, data=audio_data)< br > result = response.json()</ p >
< h1 > Print transcription</ h1 >
< p > print("Transcription:", result.get("transcription"))</ p >
< h1 > Print word-level details with speaker info</ h1 >
< p > for word in result.get("words", []):< br > speaker = word.get("speaker", "N/A")< br > print(f" [Speaker { speaker } ] [{ word [ 'start' ] :.2f}s - { word [ 'end' ] :.2f}s] { word [ 'word' ] } ")</ p >
< h1 > Check emotions</ h1 >
import os
import requests
< p > API_KEY = os.getenv("SMALLEST_API_KEY")< br > audio_file = "meeting_recording.wav"</ p >
< p > url = "< a href ="https://waves-api.smallest.ai/api/v1/pulse/get_text" data-framer-link ="Link:{" url ":"https://waves-api.smallest.ai/api/v1/pulse/get_text","type":"url"}">https://waves-api.smallest.ai/api/v1/pulse/get_text</ a > "< br > params = { < br > "model": "pulse",< br > "language": "en",< br > "word_timestamps": "true",< br > "diarize": "true",< br > "emotion_detection": "true"< br > }< br > headers = { < br > "Authorization": f"Bearer { API_KEY } ",< br > "Content-Type": "audio/wav"< br > }</ p >
< p > with open(audio_file, "rb") as f:< br > audio_data = f.read()</ p >
< p > response = requests.post(url, params=params, headers=headers, data=audio_data)< br > result = response.json()</ p >
< h1 > Print transcription</ h1 >
< p > print("Transcription:", result.get("transcription"))</ p >
< h1 > Print word-level details with speaker info</ h1 >
< p > for word in result.get("words", []):< br > speaker = word.get("speaker", "N/A")< br > print(f" [Speaker { speaker } ] [{ word [ 'start' ] :.2f}s - { word [ 'end' ] :.2f}s] { word [ 'word' ] } ")</ p >
< h1 > Check emotions</ h1 >
Node.js Example
const fs = require ( 'fs' ) ; < br > const axios = require('axios');< p > </ p >
< p > const API_KEY = process.env.SMALLEST_API_KEY;< br > const audioFile = 'meeting_recording.wav';</ p >
< p > const url = '< a href ="https://waves-api.smallest.ai/api/v1/pulse/get_text" data-framer-link ="Link:{" url ":"https://waves-api.smallest.ai/api/v1/pulse/get_text","type":"url"}">https://waves-api.smallest.ai/api/v1/pulse/get_text</ a > ';< br > const params = new URLSearchParams({ < br > model: 'pulse',< br > language: 'en',< br > word_timestamps: 'true',< br > diarize: 'true',< br > emotion_detection: 'true'< br > });</ p >
< p > const audioData = fs.readFileSync(audioFile);</ p >
< p > axios.post(< code > ${ url } ?${ params } </ code > , audioData, { < br > headers: { < br > 'Authorization': < code > Bearer ${ API_KEY } </ code > ,< br > 'Content-Type': 'audio/wav'< br > }< br > })< br > .then(res => { < br > console.log('Transcription:', res.data.transcription);</ p >
const fs = require ( 'fs' ) ; < br > const axios = require('axios');< p > </ p >
< p > const API_KEY = process.env.SMALLEST_API_KEY;< br > const audioFile = 'meeting_recording.wav';</ p >
< p > const url = '< a href ="https://waves-api.smallest.ai/api/v1/pulse/get_text" data-framer-link ="Link:{" url ":"https://waves-api.smallest.ai/api/v1/pulse/get_text","type":"url"}">https://waves-api.smallest.ai/api/v1/pulse/get_text</ a > ';< br > const params = new URLSearchParams({ < br > model: 'pulse',< br > language: 'en',< br > word_timestamps: 'true',< br > diarize: 'true',< br > emotion_detection: 'true'< br > });</ p >
< p > const audioData = fs.readFileSync(audioFile);</ p >
< p > axios.post(< code > ${ url } ?${ params } </ code > , audioData, { < br > headers: { < br > 'Authorization': < code > Bearer ${ API_KEY } </ code > ,< br > 'Content-Type': 'audio/wav'< br > }< br > })< br > .then(res => { < br > console.log('Transcription:', res.data.transcription);</ p >
const fs = require ( 'fs' ) ; < br > const axios = require('axios');< p > </ p >
< p > const API_KEY = process.env.SMALLEST_API_KEY;< br > const audioFile = 'meeting_recording.wav';</ p >
< p > const url = '< a href ="https://waves-api.smallest.ai/api/v1/pulse/get_text" data-framer-link ="Link:{" url ":"https://waves-api.smallest.ai/api/v1/pulse/get_text","type":"url"}">https://waves-api.smallest.ai/api/v1/pulse/get_text</ a > ';< br > const params = new URLSearchParams({ < br > model: 'pulse',< br > language: 'en',< br > word_timestamps: 'true',< br > diarize: 'true',< br > emotion_detection: 'true'< br > });</ p >
< p > const audioData = fs.readFileSync(audioFile);</ p >
< p > axios.post(< code > ${ url } ?${ params } </ code > , audioData, { < br > headers: { < br > 'Authorization': < code > Bearer ${ API_KEY } </ code > ,< br > 'Content-Type': 'audio/wav'< br > }< br > })< br > .then(res => { < br > console.log('Transcription:', res.data.transcription);</ p >
Example Response
{ < br > "status": "success",< br > "transcription": "Hello, this is a test transcription.",< br > "words": [< br > { "start" : 0.0, "end": 0.88, "word": "Hello,", "confidence": 0.82, "speaker": 0, "speaker_confidence": 0.61},< br > { "start" : 0.88, "end": 1.04, "word": "this", "confidence": 1.0, "speaker": 0, "speaker_confidence": 0.76},< br > { "start" : 1.04, "end": 1.20, "word": "is", "confidence": 1.0, "speaker": 0, "speaker_confidence": 0.99},< br > { "start" : 1.20, "end": 1.36, "word": "a", "confidence": 1.0, "speaker": 0, "speaker_confidence": 0.99},< br > { "start" : 1.36, "end": 1.68, "word": "test", "confidence": 0.99, "speaker": 0, "speaker_confidence": 0.99},< br > { "start" : 1.68, "end": 2.16, "word": "transcription.", "confidence": 0.99, "speaker": 0, "speaker_confidence": 0.99}< br > ],< br > "utterances": [< br > { "start" : 0.0, "end": 2.16, "text": "Hello, this is a test transcription.", "speaker": 0}< br > ],< br > "age": "adult",< br > "gender": "female",< br > "emotions": { < br > "happiness": 0.28,< br > "sadness": 0.0,< br > "anger": 0.0,< br > "fear": 0.0,< br > "disgust": 0.0< br > },< br > "metadata": { < br > "duration": 1.97,< br > "fileSize": 63236< br > }< br >
{ < br > "status": "success",< br > "transcription": "Hello, this is a test transcription.",< br > "words": [< br > { "start" : 0.0, "end": 0.88, "word": "Hello,", "confidence": 0.82, "speaker": 0, "speaker_confidence": 0.61},< br > { "start" : 0.88, "end": 1.04, "word": "this", "confidence": 1.0, "speaker": 0, "speaker_confidence": 0.76},< br > { "start" : 1.04, "end": 1.20, "word": "is", "confidence": 1.0, "speaker": 0, "speaker_confidence": 0.99},< br > { "start" : 1.20, "end": 1.36, "word": "a", "confidence": 1.0, "speaker": 0, "speaker_confidence": 0.99},< br > { "start" : 1.36, "end": 1.68, "word": "test", "confidence": 0.99, "speaker": 0, "speaker_confidence": 0.99},< br > { "start" : 1.68, "end": 2.16, "word": "transcription.", "confidence": 0.99, "speaker": 0, "speaker_confidence": 0.99}< br > ],< br > "utterances": [< br > { "start" : 0.0, "end": 2.16, "text": "Hello, this is a test transcription.", "speaker": 0}< br > ],< br > "age": "adult",< br > "gender": "female",< br > "emotions": { < br > "happiness": 0.28,< br > "sadness": 0.0,< br > "anger": 0.0,< br > "fear": 0.0,< br > "disgust": 0.0< br > },< br > "metadata": { < br > "duration": 1.97,< br > "fileSize": 63236< br > }< br >
{ < br > "status": "success",< br > "transcription": "Hello, this is a test transcription.",< br > "words": [< br > { "start" : 0.0, "end": 0.88, "word": "Hello,", "confidence": 0.82, "speaker": 0, "speaker_confidence": 0.61},< br > { "start" : 0.88, "end": 1.04, "word": "this", "confidence": 1.0, "speaker": 0, "speaker_confidence": 0.76},< br > { "start" : 1.04, "end": 1.20, "word": "is", "confidence": 1.0, "speaker": 0, "speaker_confidence": 0.99},< br > { "start" : 1.20, "end": 1.36, "word": "a", "confidence": 1.0, "speaker": 0, "speaker_confidence": 0.99},< br > { "start" : 1.36, "end": 1.68, "word": "test", "confidence": 0.99, "speaker": 0, "speaker_confidence": 0.99},< br > { "start" : 1.68, "end": 2.16, "word": "transcription.", "confidence": 0.99, "speaker": 0, "speaker_confidence": 0.99}< br > ],< br > "utterances": [< br > { "start" : 0.0, "end": 2.16, "text": "Hello, this is a test transcription.", "speaker": 0}< br > ],< br > "age": "adult",< br > "gender": "female",< br > "emotions": { < br > "happiness": 0.28,< br > "sadness": 0.0,< br > "anger": 0.0,< br > "fear": 0.0,< br > "disgust": 0.0< br > },< br > "metadata": { < br > "duration": 1.97,< br > "fileSize": 63236< br > }< br >
Part 2: Real-Time Streaming (WebSocket API) For live audio – voice assistants, live captioning, call center analytics – use the WebSocket API for sub-second latency with partial results as audio streams in.
WebSocket Endpoint Query Parameters Parameter
Type
Default
Description
language
string
en
Language code or multi for auto-detect
encoding
string
linear16
Audio format: linear16, linear32, alaw, mulaw, opus
sample_rate
string
16000
Sample rate: 8000, 16000, 22050, 24000, 44100, 48000
word_timestamps
string
true
Include word-level timestamps
full_transcript
string
false
Include cumulative transcript
sentence_timestamps
string
false
Include sentence-level timestamps
redact_pii
string
false
Redact personal information
redact_pci
string
false
Redact payment card information
diarize
string
false
Enable speaker diarization
Python Streaming Example From the official cookbook :
import asyncio < br > import json< br > import os< br > import numpy as np< br > import websockets< br > import librosa< br > from urllib.parse import urlencode< p > </ p >
< p > WS_URL = "wss://waves-api.smallest.ai/api/v1/pulse/get_text"</ p >
< h1 > Configurable features</ h1 >
< p > LANGUAGE = "en"< br > ENCODING = "linear16"< br > SAMPLE_RATE = 16000< br > WORD_TIMESTAMPS = False< br > FULL_TRANSCRIPT = True< br > SENTENCE_TIMESTAMPS = False< br > DIARIZE = False< br > REDACT_PII = False< br > REDACT_PCI = False</ p >
< p > async def transcribe(audio_file: str, api_key: str):< br > params = { < br > "language": LANGUAGE,< br > "encoding": ENCODING,< br > "sample_rate": SAMPLE_RATE,< br > "word_timestamps": str(WORD_TIMESTAMPS).lower(),< br > "full_transcript": str(FULL_TRANSCRIPT).lower(),< br > "sentence_timestamps": str(SENTENCE_TIMESTAMPS).lower(),< br > "diarize": str(DIARIZE).lower(),< br > "redact_pii": str(REDACT_PII).lower(),< br > "redact_pci": str(REDACT_PCI).lower(),< br > }</ p >
< pre > < code > url = f"{ WS_URL } ?{ urlencode ( params ) } "
headers = { "Authorization" : f"Bearer { api_key } "}
# Load audio with librosa (handles any format)
audio, _ = librosa.load(audio_file, sr=SAMPLE_RATE, mono=True)
chunk_duration = 0.1 # 100ms chunks
chunk_size = int(chunk_duration * SAMPLE_RATE)
async with websockets.connect(url, additional_headers=headers) as ws:
print("✅ Connected to Pulse STT WebSocket")
async def send_audio():
for i in range(0, len(audio), chunk_size):
chunk = audio[i:i + chunk_size]
pcm16 = (chunk * 32768.0).astype(np.int16).tobytes()
await ws.send(pcm16)
await asyncio.sleep(chunk_duration)
await ws.send(json.dumps({ "type" : "end"}))
print("📤 Sent end signal")
async def receive_responses():
async for message in ws:
result = json.loads(message)
if result.get("is_final"):
print(f"✓ { result .get ( 'transcript' ) } ")
if result.get("is_last"):
if result.get("full_transcript"):
print(f"\n{ '=' *60 } ")
print("FULL TRANSCRIPT")
print(f"{ '=' *60 } ")
print(result.get("full_transcript"))
break
await asyncio.gather(send_audio(), receive_responses())
</ code > </ pre >
< h1 > Usage</ h1 >
import asyncio < br > import json< br > import os< br > import numpy as np< br > import websockets< br > import librosa< br > from urllib.parse import urlencode< p > </ p >
< p > WS_URL = "wss://waves-api.smallest.ai/api/v1/pulse/get_text"</ p >
< h1 > Configurable features</ h1 >
< p > LANGUAGE = "en"< br > ENCODING = "linear16"< br > SAMPLE_RATE = 16000< br > WORD_TIMESTAMPS = False< br > FULL_TRANSCRIPT = True< br > SENTENCE_TIMESTAMPS = False< br > DIARIZE = False< br > REDACT_PII = False< br > REDACT_PCI = False</ p >
< p > async def transcribe(audio_file: str, api_key: str):< br > params = { < br > "language": LANGUAGE,< br > "encoding": ENCODING,< br > "sample_rate": SAMPLE_RATE,< br > "word_timestamps": str(WORD_TIMESTAMPS).lower(),< br > "full_transcript": str(FULL_TRANSCRIPT).lower(),< br > "sentence_timestamps": str(SENTENCE_TIMESTAMPS).lower(),< br > "diarize": str(DIARIZE).lower(),< br > "redact_pii": str(REDACT_PII).lower(),< br > "redact_pci": str(REDACT_PCI).lower(),< br > }</ p >
< pre > < code > url = f"{ WS_URL } ?{ urlencode ( params ) } "
headers = { "Authorization" : f"Bearer { api_key } "}
# Load audio with librosa (handles any format)
audio, _ = librosa.load(audio_file, sr=SAMPLE_RATE, mono=True)
chunk_duration = 0.1 # 100ms chunks
chunk_size = int(chunk_duration * SAMPLE_RATE)
async with websockets.connect(url, additional_headers=headers) as ws:
print("✅ Connected to Pulse STT WebSocket")
async def send_audio():
for i in range(0, len(audio), chunk_size):
chunk = audio[i:i + chunk_size]
pcm16 = (chunk * 32768.0).astype(np.int16).tobytes()
await ws.send(pcm16)
await asyncio.sleep(chunk_duration)
await ws.send(json.dumps({ "type" : "end"}))
print("📤 Sent end signal")
async def receive_responses():
async for message in ws:
result = json.loads(message)
if result.get("is_final"):
print(f"✓ { result .get ( 'transcript' ) } ")
if result.get("is_last"):
if result.get("full_transcript"):
print(f"\n{ '=' *60 } ")
print("FULL TRANSCRIPT")
print(f"{ '=' *60 } ")
print(result.get("full_transcript"))
break
await asyncio.gather(send_audio(), receive_responses())
</ code > </ pre >
< h1 > Usage</ h1 >
import asyncio < br > import json< br > import os< br > import numpy as np< br > import websockets< br > import librosa< br > from urllib.parse import urlencode< p > </ p >
< p > WS_URL = "wss://waves-api.smallest.ai/api/v1/pulse/get_text"</ p >
< h1 > Configurable features</ h1 >
< p > LANGUAGE = "en"< br > ENCODING = "linear16"< br > SAMPLE_RATE = 16000< br > WORD_TIMESTAMPS = False< br > FULL_TRANSCRIPT = True< br > SENTENCE_TIMESTAMPS = False< br > DIARIZE = False< br > REDACT_PII = False< br > REDACT_PCI = False</ p >
< p > async def transcribe(audio_file: str, api_key: str):< br > params = { < br > "language": LANGUAGE,< br > "encoding": ENCODING,< br > "sample_rate": SAMPLE_RATE,< br > "word_timestamps": str(WORD_TIMESTAMPS).lower(),< br > "full_transcript": str(FULL_TRANSCRIPT).lower(),< br > "sentence_timestamps": str(SENTENCE_TIMESTAMPS).lower(),< br > "diarize": str(DIARIZE).lower(),< br > "redact_pii": str(REDACT_PII).lower(),< br > "redact_pci": str(REDACT_PCI).lower(),< br > }</ p >
< pre > < code > url = f"{ WS_URL } ?{ urlencode ( params ) } "
headers = { "Authorization" : f"Bearer { api_key } "}
# Load audio with librosa (handles any format)
audio, _ = librosa.load(audio_file, sr=SAMPLE_RATE, mono=True)
chunk_duration = 0.1 # 100ms chunks
chunk_size = int(chunk_duration * SAMPLE_RATE)
async with websockets.connect(url, additional_headers=headers) as ws:
print("✅ Connected to Pulse STT WebSocket")
async def send_audio():
for i in range(0, len(audio), chunk_size):
chunk = audio[i:i + chunk_size]
pcm16 = (chunk * 32768.0).astype(np.int16).tobytes()
await ws.send(pcm16)
await asyncio.sleep(chunk_duration)
await ws.send(json.dumps({ "type" : "end"}))
print("📤 Sent end signal")
async def receive_responses():
async for message in ws:
result = json.loads(message)
if result.get("is_final"):
print(f"✓ { result .get ( 'transcript' ) } ")
if result.get("is_last"):
if result.get("full_transcript"):
print(f"\n{ '=' *60 } ")
print("FULL TRANSCRIPT")
print(f"{ '=' *60 } ")
print(result.get("full_transcript"))
break
await asyncio.gather(send_audio(), receive_responses())
</ code > </ pre >
< h1 > Usage</ h1 >
Install dependencies:
Run:
export SMALLEST_API_KEY ="your-api-key" <br >python transcribe .py recording .wav
export SMALLEST_API_KEY ="your-api-key" <br >python transcribe .py recording .wav
export SMALLEST_API_KEY ="your-api-key" <br >python transcribe .py recording .wav
(Preview output)
Node.js Streaming Example From the official cookbook :
const fs = require ( "fs" ) ; < br > const WebSocket = require("ws");< br > const wav = require("wav");< p > </ p >
< p > const WS_URL = "wss://waves-api.smallest.ai/api/v1/pulse/get_text";</ p >
< p > // Configurable features< br > const LANGUAGE = "en";< br > const ENCODING = "linear16";< br > const SAMPLE_RATE = 16000;< br > const WORD_TIMESTAMPS = false;< br > const FULL_TRANSCRIPT = true;< br > const DIARIZE = false;< br > const REDACT_PII = false;< br > const REDACT_PCI = false;</ p >
< p > async function loadAudio(audioFile) { < br > return new Promise((resolve, reject) => { < br > const reader = new wav.Reader();< br > const chunks = [];</ p >
< pre > < code > reader.on("format", (format) => {
reader .on ( "data" , ( chunk ) => chunks.push(chunk));
reader.on("end", () => {
const buffer = Buffer.concat(chunks);
const samples = new Int16Array(buffer.buffer, buffer.byteOffset, buffer.length / 2);
resolve(samples);
});
});
reader.on("error", reject);
fs.createReadStream(audioFile).pipe(reader);
</ code > </ pre >
< p > });< br > }</ p >
< p > async function transcribe(audioFile, apiKey) { < br > const params = new URLSearchParams({ < br > language: LANGUAGE,< br > encoding: ENCODING,< br > sample_rate: SAMPLE_RATE,< br > word_timestamps: WORD_TIMESTAMPS,< br > full_transcript: FULL_TRANSCRIPT,< br > diarize: DIARIZE,< br > redact_pii: REDACT_PII,< br > redact_pci: REDACT_PCI,< br > });</ p >
< p > const url = < code > ${ WS_URL } ?${ params } </ code > ;< br > const audio = await loadAudio(audioFile);< br > const chunkDuration = 0.1; // 100ms< br > const chunkSize = Math.floor(chunkDuration * SAMPLE_RATE);</ p >
< p > return new Promise((resolve, reject) => { < br > const ws = new WebSocket(url, { < br > headers: { Authorization : < code > Bearer ${ apiKey } </ code > },< br > });</ p >
< pre > < code > ws.on("open", async () => {
console .log ( "✅ Connected to Pulse STT WebSocket" ) ;
for (let i = 0; i < audio.length; i += chunkSize) {
const chunk = audio.slice(i, i + chunkSize);
ws.send(Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength));
await new Promise((r) => setTimeout(r, chunkDuration * 1000));
}
ws.send(JSON.stringify({ type : "end" }));
console.log("📤 Sent end signal");
});
ws.on("message", (data) => {
const result = JSON.parse(data.toString());
if (result.is_final) {
console .log ( `✓ ${ result .transcript } ` ) ;
if (result.is_last) {
if (result.full_transcript) {
console .log ( "\n" + "=" .repeat ( 60 ) ) ;
console.log("FULL TRANSCRIPT");
console.log("=".repeat(60));
console.log(result.full_transcript);
}
ws.close();
}
}
});
ws.on("close", resolve);
ws.on("error", reject);
</ code > </ pre >
< p > });< br > }</ p >
const fs = require ( "fs" ) ; < br > const WebSocket = require("ws");< br > const wav = require("wav");< p > </ p >
< p > const WS_URL = "wss://waves-api.smallest.ai/api/v1/pulse/get_text";</ p >
< p > // Configurable features< br > const LANGUAGE = "en";< br > const ENCODING = "linear16";< br > const SAMPLE_RATE = 16000;< br > const WORD_TIMESTAMPS = false;< br > const FULL_TRANSCRIPT = true;< br > const DIARIZE = false;< br > const REDACT_PII = false;< br > const REDACT_PCI = false;</ p >
< p > async function loadAudio(audioFile) { < br > return new Promise((resolve, reject) => { < br > const reader = new wav.Reader();< br > const chunks = [];</ p >
< pre > < code > reader.on("format", (format) => {
reader .on ( "data" , ( chunk ) => chunks.push(chunk));
reader.on("end", () => {
const buffer = Buffer.concat(chunks);
const samples = new Int16Array(buffer.buffer, buffer.byteOffset, buffer.length / 2);
resolve(samples);
});
});
reader.on("error", reject);
fs.createReadStream(audioFile).pipe(reader);
</ code > </ pre >
< p > });< br > }</ p >
< p > async function transcribe(audioFile, apiKey) { < br > const params = new URLSearchParams({ < br > language: LANGUAGE,< br > encoding: ENCODING,< br > sample_rate: SAMPLE_RATE,< br > word_timestamps: WORD_TIMESTAMPS,< br > full_transcript: FULL_TRANSCRIPT,< br > diarize: DIARIZE,< br > redact_pii: REDACT_PII,< br > redact_pci: REDACT_PCI,< br > });</ p >
< p > const url = < code > ${ WS_URL } ?${ params } </ code > ;< br > const audio = await loadAudio(audioFile);< br > const chunkDuration = 0.1; // 100ms< br > const chunkSize = Math.floor(chunkDuration * SAMPLE_RATE);</ p >
< p > return new Promise((resolve, reject) => { < br > const ws = new WebSocket(url, { < br > headers: { Authorization : < code > Bearer ${ apiKey } </ code > },< br > });</ p >
< pre > < code > ws.on("open", async () => {
console .log ( "✅ Connected to Pulse STT WebSocket" ) ;
for (let i = 0; i < audio.length; i += chunkSize) {
const chunk = audio.slice(i, i + chunkSize);
ws.send(Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength));
await new Promise((r) => setTimeout(r, chunkDuration * 1000));
}
ws.send(JSON.stringify({ type : "end" }));
console.log("📤 Sent end signal");
});
ws.on("message", (data) => {
const result = JSON.parse(data.toString());
if (result.is_final) {
console .log ( `✓ ${ result .transcript } ` ) ;
if (result.is_last) {
if (result.full_transcript) {
console .log ( "\n" + "=" .repeat ( 60 ) ) ;
console.log("FULL TRANSCRIPT");
console.log("=".repeat(60));
console.log(result.full_transcript);
}
ws.close();
}
}
});
ws.on("close", resolve);
ws.on("error", reject);
</ code > </ pre >
< p > });< br > }</ p >
const fs = require ( "fs" ) ; < br > const WebSocket = require("ws");< br > const wav = require("wav");< p > </ p >
< p > const WS_URL = "wss://waves-api.smallest.ai/api/v1/pulse/get_text";</ p >
< p > // Configurable features< br > const LANGUAGE = "en";< br > const ENCODING = "linear16";< br > const SAMPLE_RATE = 16000;< br > const WORD_TIMESTAMPS = false;< br > const FULL_TRANSCRIPT = true;< br > const DIARIZE = false;< br > const REDACT_PII = false;< br > const REDACT_PCI = false;</ p >
< p > async function loadAudio(audioFile) { < br > return new Promise((resolve, reject) => { < br > const reader = new wav.Reader();< br > const chunks = [];</ p >
< pre > < code > reader.on("format", (format) => {
reader .on ( "data" , ( chunk ) => chunks.push(chunk));
reader.on("end", () => {
const buffer = Buffer.concat(chunks);
const samples = new Int16Array(buffer.buffer, buffer.byteOffset, buffer.length / 2);
resolve(samples);
});
});
reader.on("error", reject);
fs.createReadStream(audioFile).pipe(reader);
</ code > </ pre >
< p > });< br > }</ p >
< p > async function transcribe(audioFile, apiKey) { < br > const params = new URLSearchParams({ < br > language: LANGUAGE,< br > encoding: ENCODING,< br > sample_rate: SAMPLE_RATE,< br > word_timestamps: WORD_TIMESTAMPS,< br > full_transcript: FULL_TRANSCRIPT,< br > diarize: DIARIZE,< br > redact_pii: REDACT_PII,< br > redact_pci: REDACT_PCI,< br > });</ p >
< p > const url = < code > ${ WS_URL } ?${ params } </ code > ;< br > const audio = await loadAudio(audioFile);< br > const chunkDuration = 0.1; // 100ms< br > const chunkSize = Math.floor(chunkDuration * SAMPLE_RATE);</ p >
< p > return new Promise((resolve, reject) => { < br > const ws = new WebSocket(url, { < br > headers: { Authorization : < code > Bearer ${ apiKey } </ code > },< br > });</ p >
< pre > < code > ws.on("open", async () => {
console .log ( "✅ Connected to Pulse STT WebSocket" ) ;
for (let i = 0; i < audio.length; i += chunkSize) {
const chunk = audio.slice(i, i + chunkSize);
ws.send(Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength));
await new Promise((r) => setTimeout(r, chunkDuration * 1000));
}
ws.send(JSON.stringify({ type : "end" }));
console.log("📤 Sent end signal");
});
ws.on("message", (data) => {
const result = JSON.parse(data.toString());
if (result.is_final) {
console .log ( `✓ ${ result .transcript } ` ) ;
if (result.is_last) {
if (result.full_transcript) {
console .log ( "\n" + "=" .repeat ( 60 ) ) ;
console.log("FULL TRANSCRIPT");
console.log("=".repeat(60));
console.log(result.full_transcript);
}
ws.close();
}
}
});
ws.on("close", resolve);
ws.on("error", reject);
</ code > </ pre >
< p > });< br > }</ p >
Install dependencies:
Run:
export SMALLEST_API_KEY ="your-api-key" <br >node transcribe .js recording .wav
export SMALLEST_API_KEY ="your-api-key" <br >node transcribe .js recording .wav
export SMALLEST_API_KEY ="your-api-key" <br >node transcribe .js recording .wav
WebSocket Response Format
{ < br > "session_id": "sess_12345abcde",< br > "transcript": "Hello, how are you?",< br > "full_transcript": "Hello, how are you?",< br > "is_final": true,< br > "is_last": false,< br > "language": "en",< br > "words": [< br > { "word" : "Hello,", "start": 0.0, "end": 0.5, "confidence": 0.98, "speaker": 0},< br > { "word" : "how", "start": 0.5, "end": 0.7, "confidence": 0.99, "speaker": 0},< br > { "word" : "are", "start": 0.7, "end": 0.9, "confidence": 0.97, "speaker": 0},< br > { "word" : "you?", "start": 0.9, "end": 1.2, "confidence": 0.99, "speaker": 0}< br > ]< br >
{ < br > "session_id": "sess_12345abcde",< br > "transcript": "Hello, how are you?",< br > "full_transcript": "Hello, how are you?",< br > "is_final": true,< br > "is_last": false,< br > "language": "en",< br > "words": [< br > { "word" : "Hello,", "start": 0.0, "end": 0.5, "confidence": 0.98, "speaker": 0},< br > { "word" : "how", "start": 0.5, "end": 0.7, "confidence": 0.99, "speaker": 0},< br > { "word" : "are", "start": 0.7, "end": 0.9, "confidence": 0.97, "speaker": 0},< br > { "word" : "you?", "start": 0.9, "end": 1.2, "confidence": 0.99, "speaker": 0}< br > ]< br >
{ < br > "session_id": "sess_12345abcde",< br > "transcript": "Hello, how are you?",< br > "full_transcript": "Hello, how are you?",< br > "is_final": true,< br > "is_last": false,< br > "language": "en",< br > "words": [< br > { "word" : "Hello,", "start": 0.0, "end": 0.5, "confidence": 0.98, "speaker": 0},< br > { "word" : "how", "start": 0.5, "end": 0.7, "confidence": 0.99, "speaker": 0},< br > { "word" : "are", "start": 0.7, "end": 0.9, "confidence": 0.97, "speaker": 0},< br > { "word" : "you?", "start": 0.9, "end": 1.2, "confidence": 0.99, "speaker": 0}< br > ]< br >
Key Response Fields Field
Description
is_final
false = partial/interim transcript; true = finalized segment
is_last
true when the entire session is complete
transcript
Current segment text
full_transcript
Accumulated text from entire session (if enabled)
words
Word-level timestamps (if enabled)
Part 3: Advanced Features
Speaker Diarization Enable diarize=true to identify different speakers:
params = { "model" : "pulse" , "language" : "en" , "diarize" : "true" }
params = { "model" : "pulse" , "language" : "en" , "diarize" : "true" }
params = { "model" : "pulse" , "language" : "en" , "diarize" : "true" }
Response includes speaker labels:
{ < br > "words": [< br > { "word" : "Hello", "speaker": 0, "speaker_confidence": 0.95},< br > { "word" : "Hi", "speaker": 1, "speaker_confidence": 0.92}< br > ],< br > "utterances": [< br > { "text" : "Hello, how can I help?", "speaker": 0},< br > { "text" : "I have a question.", "speaker": 1}< br > ]< br >
{ < br > "words": [< br > { "word" : "Hello", "speaker": 0, "speaker_confidence": 0.95},< br > { "word" : "Hi", "speaker": 1, "speaker_confidence": 0.92}< br > ],< br > "utterances": [< br > { "text" : "Hello, how can I help?", "speaker": 0},< br > { "text" : "I have a question.", "speaker": 1}< br > ]< br >
{ < br > "words": [< br > { "word" : "Hello", "speaker": 0, "speaker_confidence": 0.95},< br > { "word" : "Hi", "speaker": 1, "speaker_confidence": 0.92}< br > ],< br > "utterances": [< br > { "text" : "Hello, how can I help?", "speaker": 0},< br > { "text" : "I have a question.", "speaker": 1}< br > ]< br >
Emotion Detection Enable emotion_detection=true to analyze speaker sentiment:
{ < br > "emotions": { < br > "happiness": 0.28,< br > "sadness": 0.0,< br > "anger": 0.0,< br > "fear": 0.0,< br > "disgust": 0.0< br > }< br >
{ < br > "emotions": { < br > "happiness": 0.28,< br > "sadness": 0.0,< br > "anger": 0.0,< br > "fear": 0.0,< br > "disgust": 0.0< br > }< br >
{ < br > "emotions": { < br > "happiness": 0.28,< br > "sadness": 0.0,< br > "anger": 0.0,< br > "fear": 0.0,< br > "disgust": 0.0< br > }< br >
PII/PCI Redaction For compliance (HIPAA, PCI-DSS), enable redact_pii=true or redact_pci=true:
{ < br > "transcript": "My credit card is [CREDITCARD_1] and SSN is [SSN_1]",< br > "redacted_entities": ["[CREDITCARD_1]", "[SSN_1]"]< br >
{ < br > "transcript": "My credit card is [CREDITCARD_1] and SSN is [SSN_1]",< br > "redacted_entities": ["[CREDITCARD_1]", "[SSN_1]"]< br >
{ < br > "transcript": "My credit card is [CREDITCARD_1] and SSN is [SSN_1]",< br > "redacted_entities": ["[CREDITCARD_1]", "[SSN_1]"]< br >
Age and Gender Detection Enable age_detection=true and gender_detection=true:
{ < br > "age": "adult",< br > "gender": "female"< br >
{ < br > "age": "adult",< br > "gender": "female"< br >
{ < br > "age": "adult",< br > "gender": "female"< br >
Comparing STT Providers Provider
Latency
Languages
Diarization
Emotion
PII Redaction
Price (per 1000 min)
Pulse STT
64ms
32+
✅
✅
✅
Competitive
Google Cloud STT
200-300ms
125+
✅
❌
❌
$16
Deepgram
100-200ms
36+
✅
❌
✅
$4-5
AssemblyAI
200-400ms
30+
✅
✅
✅
$3.50
OpenAI Whisper
Batch only
99+
❌
❌
❌
~$6
Why Pulse STT stands out:
Fastest time-to-first-transcript (64ms)
All-in-one features (no separate services needed)
Competitive accuracy across diverse accents
Built for real-time voice AI applications
Best Practices Audio Quality Use 16kHz, mono, 16-bit PCM for best results
WAV or FLAC formats are ideal
Minimize background noise when possible
Error Handling try : < br > response = requests.post(url, params=params, headers=headers, data=audio_data, timeout=120)< br > response.raise_for_status()< br > except requests.exceptions.HTTPError as e:< br > if e.response.status_code == 429:< br > # Rate limited - implement exponential backoff< br > time.sleep(2 ** retry_count)< br > elif e.response.status_code == 401:< br > # Invalid API key< br >
try : < br > response = requests.post(url, params=params, headers=headers, data=audio_data, timeout=120)< br > response.raise_for_status()< br > except requests.exceptions.HTTPError as e:< br > if e.response.status_code == 429:< br > # Rate limited - implement exponential backoff< br > time.sleep(2 ** retry_count)< br > elif e.response.status_code == 401:< br > # Invalid API key< br >
try : < br > response = requests.post(url, params=params, headers=headers, data=audio_data, timeout=120)< br > response.raise_for_status()< br > except requests.exceptions.HTTPError as e:< br > if e.response.status_code == 429:< br > # Rate limited - implement exponential backoff< br > time.sleep(2 ** retry_count)< br > elif e.response.status_code == 401:< br > # Invalid API key< br >
Rate Limiting Add 500ms+ delay between batch requests
Use webhooks for long audio files
Implement exponential backoff for 429 errors
Bonus: Full Demo Application Want to see everything working together? Check out the demo app in the code samples repository — a complete Next.js web application featuring:
File upload transcription with word-level timestamps (hover to see timing)
Real-time microphone streaming with live transcript display
Secure WebSocket proxy that keeps your API key server-side
Modern UI with Smallest AI brand colors
Language selection (English, Hindi, Spanish, French, German, Portuguese, Auto-detect)
Emotion detection and speaker diarization display
Quick Start cd demo -app <br >npm install
cd demo -app <br >npm install
cd demo -app <br >npm install
Create a .env.local file with your API key:
echo 'SMALLEST_API_KEY=your-api-key' > .env .local
echo 'SMALLEST_API_KEY=your-api-key' > .env .local
echo 'SMALLEST_API_KEY=your-api-key' > .env .local
Start both servers (Next.js + WebSocket proxy):
Then open http://localhost:3000 in Chrome or Safari (for microphone access).
How It Works The demo runs two servers:
Next.js (port 3000) — Serves the React UI and handles file upload via /api/transcribe
WebSocket Proxy (port 3001) — Securely proxies audio from browser to Pulse STT WebSocket API
Browser → WebSocket Proxy (3001) → Pulse STT (wss://waves-api.smallest.ai)< br >
Browser → WebSocket Proxy (3001) → Pulse STT (wss://waves-api.smallest.ai)< br >
Browser → WebSocket Proxy (3001) → Pulse STT (wss://waves-api.smallest.ai)< br >
This architecture keeps your API key secure on the server while enabling real-time streaming.
Project Structure demo-app/< br > ├── src/< br > │ └── app/< br > │ ├── api/< br > │ │ └── transcribe/< br > │ │ └── route.ts # REST API for file upload< br > │ ├── page.tsx # Main UI< br > │ └── layout.tsx< br > ├── ws-server.js # WebSocket proxy server< br > ├── .env.local # Your API key (create this)< br >
demo-app/< br > ├── src/< br > │ └── app/< br > │ ├── api/< br > │ │ └── transcribe/< br > │ │ └── route.ts # REST API for file upload< br > │ ├── page.tsx # Main UI< br > │ └── layout.tsx< br > ├── ws-server.js # WebSocket proxy server< br > ├── .env.local # Your API key (create this)< br >
demo-app/< br > ├── src/< br > │ └── app/< br > │ ├── api/< br > │ │ └── transcribe/< br > │ │ └── route.ts # REST API for file upload< br > │ ├── page.tsx # Main UI< br > │ └── layout.tsx< br > ├── ws-server.js # WebSocket proxy server< br > ├── .env.local # Your API key (create this)< br >
Scripts Command
Description
npm run dev
Start Next.js only
npm run dev:ws
Start WebSocket proxy only
npm run dev:all
Start both (recommended)
This architecture pattern is recommended for production apps — API keys stay server-side while the React frontend provides a smooth user experience with both file upload and real-time microphone transcription.
Conclusion Integrating voice and speech capabilities into your workflow and apps can greatly enhance user experience. With Pulse STT, developers can achieve high-accuracy, low-latency transcription with just a few API calls.
When to use REST API:
Podcast transcription
Meeting recordings
Voicemail processing
Batch analytics
When to use WebSocket API:
Live captioning
Voice assistants
Call center real-time analytics
Interactive voice applications
The code patterns in this guide translate directly to production. Start with the REST API for prototyping, then add WebSocket streaming when real-time interaction becomes a requirement.
Resources Smallest AI Console — API key management
Waves Documentation — Full API reference
Discord Community — Developer support