Node.js SDK
The perceive8 npm package is the official JavaScript and TypeScript client for the Perceive8 Audio Intelligence API. It targets Node.js 18+ and all modern browsers.
Table of Contents
- Installation
- Client Initialization
- Resources Overview
- Analyses
- Speakers
- Alerts
- Stream
- Pagination
- Error Handling
- TypeScript Models & Types
Installation
npm install perceive8
# or
yarn add perceive8
# or
pnpm add perceive8
The package ships as ESM and CommonJS with full TypeScript declarations.
Client Initialization
import { Perceive8Client } from "perceive8";
// Using an API key (recommended for server-side)
const client = new Perceive8Client({
apiKey: "pk_live_xxxxxxxxxxxx",
});
// Using a Bearer JWT (for browser / user-scoped calls)
const client = new Perceive8Client({
token: "<supabase-jwt>",
});
// Optional: override the base URL (e.g. for local development)
const client = new Perceive8Client({
apiKey: "pk_live_xxxxxxxxxxxx",
baseUrl: "http://localhost:8000",
});
Perceive8ClientOptions
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
apiKey |
string |
one of | — | API key sent as X-API-Key header |
token |
string |
one of | — | JWT token sent as Authorization: Bearer header |
baseUrl |
string |
no | https://api.perceive8.com |
Override the API base URL |
You must supply either apiKey or token. Supplying neither throws an Error immediately.
Resources Overview
The client exposes four resource namespaces:
| Property | Type | Description |
|---|---|---|
client.analyses |
AnalysesResource |
Upload, fetch, list, delete audio analyses |
client.speakers |
SpeakersResource |
Manage enrolled speaker profiles |
client.alerts |
AlertsResource |
List, acknowledge, and stream real-time alerts |
client.stream |
StreamResource |
Open a live WebSocket transcription session |
Analyses
client.analyses.upload(file, filename, language?)
Upload an audio file to create a new analysis.
const { data: analysis } = await client.analyses.upload(
new Blob([buffer], { type: "audio/mpeg" }),
"interview.mp3",
"en" // ISO 639-1 language code, default "en"
);
console.log(analysis.id); // "uuid"
console.log(analysis.status); // "pending"
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
file |
Blob | File |
— | Audio file content |
filename |
string |
— | Filename sent to the server |
language |
string |
"en" |
ISO 639-1 language code |
Returns: Promise<{ data: Analysis }>
client.analyses.list(params?)
List analyses for the authenticated user.
const { data: analyses } = await client.analyses.list({ limit: 10 });
for (const a of analyses) {
console.log(a.id, a.filename, a.status);
}
Parameters
| Parameter | Type | Description |
|---|---|---|
limit |
number |
Maximum items per page (default server-side) |
cursor |
string |
Pagination cursor from a previous response |
Returns: Promise<{ data: Analysis[] }>
client.analyses.get(analysisId)
Retrieve a single analysis by ID.
const { data: analysis } = await client.analyses.get("uuid-here");
if (analysis.status === "completed") {
console.log(analysis.transcript);
}
Returns: Promise<{ data: Analysis }>
client.analyses.delete(analysisId)
Delete an analysis permanently.
await client.analyses.delete("uuid-here");
Returns: Promise<void>
client.analyses.paginate(params?)
Async generator that yields every Analysis across all pages automatically.
for await (const analysis of client.analyses.paginate({ limit: 50 })) {
console.log(analysis.id, analysis.filename);
}
Parameters: same as list().
Returns: AsyncGenerator<Analysis>
client.analyses.streamProgress(analysisId)
Returns a browser EventSource that streams server-sent pipeline progress events for an ongoing analysis.
const es = client.analyses.streamProgress("uuid-here");
es.addEventListener("pipeline_step", (e) => {
const data = JSON.parse(e.data);
console.log(data.step, data.progress_pct, "%");
});
es.addEventListener("done", () => {
console.log("Pipeline complete");
es.close();
});
SSE Event Types
| Event name | Payload fields | Description |
|---|---|---|
pipeline_step |
step, status, progress_pct, message, timestamp |
Progress update for one pipeline step |
done |
— | All steps finished |
Returns: EventSource
Note: This method uses a
?token=query parameter to pass auth, becauseEventSourcedoes not support custom headers.
Speakers
client.speakers.list()
List enrolled speaker profiles.
const { data: speakers } = await client.speakers.list();
speakers.forEach((s) => console.log(s.id, s.name));
Returns: Promise<{ data: Speaker[] }>
client.speakers.get(speakerId)
Retrieve a single speaker.
const { data: speaker } = await client.speakers.get("speaker-uuid");
Returns: Promise<{ data: Speaker }>
client.speakers.create(name)
Create a new speaker profile.
const { data: speaker } = await client.speakers.create("Alice");
console.log(speaker.id);
Returns: Promise<{ data: Speaker }>
client.speakers.delete(speakerId)
Delete a speaker profile.
await client.speakers.delete("speaker-uuid");
Returns: Promise<void>
Alerts
client.alerts.list(analysisId, options?)
List alerts generated for a specific analysis.
const { alerts, total } = await client.alerts.list("analysis-uuid", {
acknowledged: false, // filter to unacknowledged only
});
console.log(`${total} total alerts`);
alerts.forEach((a) => console.log(a.severityLevel, a.message));
Parameters
| Parameter | Type | Description |
|---|---|---|
analysisId |
string |
The analysis to fetch alerts for |
options.acknowledged |
boolean | undefined |
Filter by acknowledged status |
Returns: Promise<{ alerts: Alert[]; total: number }>
client.alerts.acknowledge(alertId)
Mark an alert as acknowledged.
const updated = await client.alerts.acknowledge("alert-uuid");
console.log(updated.acknowledged); // true
Returns: Promise<Alert>
client.alerts.stream()
Returns an EventSource for real-time SSE alert delivery. Alerts arrive as they are triggered during live streaming sessions.
const es = client.alerts.stream();
es.addEventListener("alert", (e) => {
const alert = JSON.parse(e.data);
console.log(alert.severityLevel, alert.message);
});
// Disconnect when done
es.close();
SSE Event Types
| Event name | Payload | Description |
|---|---|---|
alert |
Alert object |
A new alert has been triggered |
Returns: EventSource
Stream
client.stream.live(config?)
Open a live WebSocket transcription session. The method connects to the WebSocket, sends the start action, and resolves only after the server responds with session_started.
const session = await client.stream.live({
language: "en",
sampleRate: 16000,
scenarioId: "toxic_language_detection",
});
console.log("Session ID:", session.sessionId);
LiveSessionConfig
| Property | Type | Default | Description |
|---|---|---|---|
language |
string |
"en" |
ISO 639-1 language code |
sampleRate |
number |
16000 |
Audio sample rate in Hz |
scenarioId |
string |
— | Optional scenario ID to enable real-time alert rules |
Returns: Promise<LiveSession>
LiveSession
A handle to an open streaming session.
session.sessionId
The server-assigned session ID (string).
session.sendAudio(pcmBuffer)
Send raw PCM audio data. Audio must be 16-bit signed integers (Int16), mono, at the sampleRate specified when the session was created.
// In a Web Audio processor callback:
const int16 = new Int16Array(float32AudioData.length);
for (let i = 0; i < float32AudioData.length; i++) {
int16[i] = Math.max(-32768, Math.min(32767, float32AudioData[i] * 32768));
}
session.sendAudio(int16.buffer);
session.on(event, handler)
Subscribe to a WebSocket event. Returns an unsubscribe function.
const unsub = session.on("transcript_final", (event) => {
console.log(event.data?.text);
});
// Later:
unsub();
Use "*" to receive all events.
Event names
| Event | Description |
|---|---|
session_started |
Session is active; data.sessionId is set |
transcript_partial |
In-progress transcript text |
transcript_final |
Committed transcript text with word timestamps |
alert |
Alert triggered by the scenario rules engine |
analysis_started |
Post-stream analysis pipeline started; analysis_id in payload |
session_ended |
Session has ended; may include analysis_id |
ping |
Server keepalive |
error |
Server-side error; data.message contains details |
session.stop()
Send a { action: "stop" } signal to the server to end the session gracefully.
session.stop();
session.close()
Immediately close the WebSocket connection.
session.close();
Full Streaming Example
import { Perceive8Client } from "perceive8";
const client = new Perceive8Client({ apiKey: process.env.PERCEIVE8_API_KEY! });
async function runLiveSession() {
const session = await client.stream.live({
language: "en",
sampleRate: 16000,
scenarioId: "sales_coaching",
});
const unsubPartial = session.on("transcript_partial", (e) => {
process.stdout.write(`\r[partial] ${e.data?.text}`);
});
const unsubFinal = session.on("transcript_final", (e) => {
console.log(`\n[final] ${e.data?.text}`);
});
session.on("alert", (e) => {
console.warn("[ALERT]", e.data);
});
session.on("session_ended", () => {
console.log("\nSession ended.");
unsubPartial();
unsubFinal();
});
// ---- Send audio here ----
// session.sendAudio(pcmInt16Buffer);
// Stop after 60 s
await new Promise((r) => setTimeout(r, 60_000));
session.stop();
session.close();
}
runLiveSession().catch(console.error);
Pagination
The paginate utility from the analyses resource handles cursor-based pagination automatically:
// Using the built-in async generator
for await (const analysis of client.analyses.paginate({ limit: 100 })) {
console.log(analysis.id);
}
For custom pagination, use the low-level paginate helper exported from the package:
import { paginate } from "perceive8";
const allAnalyses = [];
for await (const item of paginate((cursor) =>
client.analyses.list({ limit: 50, cursor })
)) {
allAnalyses.push(item);
}
The Page<T> type returned by individual list() calls:
interface Page<T> {
items: T[];
nextCursor?: string;
hasMore: boolean;
}
Error Handling
All SDK methods throw typed error subclasses:
| Class | HTTP status | Description |
|---|---|---|
Perceive8Error |
any >= 400 |
Base error class — most general |
AuthenticationError |
401 | Invalid or missing credentials |
NotFoundError |
404 | Resource does not exist |
RateLimitError |
429 | Rate limit exceeded |
The SDK automatically retries 429 responses up to 3 times with exponential back-off (1s, 2s, 4s).
import { Perceive8Error, AuthenticationError, NotFoundError, RateLimitError } from "perceive8";
try {
const { data } = await client.analyses.get("bad-id");
} catch (err) {
if (err instanceof NotFoundError) {
console.error("Analysis not found");
} else if (err instanceof AuthenticationError) {
console.error("Check your API key");
} else if (err instanceof RateLimitError) {
console.error("Slow down — rate limit hit");
} else if (err instanceof Perceive8Error) {
console.error(`API error ${err.statusCode}:`, err.response);
} else {
throw err;
}
}
Error Properties
class Perceive8Error extends Error {
statusCode?: number; // HTTP status code
response?: unknown; // Parsed JSON body from the API
}
TypeScript Models & Types
All types are exported from the perceive8 package root.
Analysis
type AnalysisStatus = "pending" | "processing" | "completed" | "failed";
interface Analysis {
id: string;
user_id: string;
filename: string;
status: AnalysisStatus;
created_at: string; // ISO 8601
completed_at?: string; // ISO 8601, set when status = "completed"
transcript?: unknown; // Full transcript object when completed
}
Speaker
interface Speaker {
id: string;
user_id: string;
name: string;
created_at: string;
}
Alert
interface Alert {
id: string;
analysisId: string;
userId: string;
scenarioId?: string;
severityLevel: "INFO" | "WARNING" | "HIGH" | "CRITICAL";
message: string;
evidenceText?: string;
speakerName?: string;
startTime?: number;
endTime?: number;
confidence?: number;
acknowledged: boolean;
createdAt: string;
}
PipelineEvent
interface PipelineEvent {
analysisId: string;
step: string;
status: "started" | "completed" | "failed";
progressPct: number;
message: string;
timestamp: string;
}
TranscriptSegment
interface TranscriptSegment {
text: string;
isFinal: boolean;
audioStart: number;
audioEnd: number;
confidence: number;
words: Array<{
text: string;
start: number;
end: number;
confidence: number;
}>;
}
StreamSession
interface StreamSession {
sessionId: string;
expiresAt: string;
}
TranscriptEvent (WebSocket message)
interface TranscriptEvent {
event:
| "transcript_partial"
| "transcript_final"
| "alert"
| "session_started"
| "session_ended"
| "ping";
data?: {
text?: string;
isFinal?: boolean;
audioStart?: number;
audioEnd?: number;
confidence?: number;
words?: Array<{ text: string; start: number; end: number; confidence: number }>;
sessionId?: string;
[key: string]: unknown;
};
sessionId?: string;
}
LiveSessionConfig
interface LiveSessionConfig {
language?: string; // default "en"
sampleRate?: number; // default 16000
scenarioId?: string;
}
ApiKey
interface ApiKey {
id: string;
name: string;
key_prefix: string;
scopes: string[];
rate_limit_rpm: number;
is_active: boolean;
created_at: string;
}
Page<T>
interface Page<T> {
items: T[];
nextCursor?: string;
hasMore: boolean;
}