Integration
Add ProsodyAI to your backend, handle errors, and receive webhooks
import { ProsodyClient } from '@prosodyai/sdk';
const prosody = new ProsodyClient({
apiKey: process.env.PROSODY_API_KEY!,
});
const result = await prosody.transcribe('./call.wav');All requests go to https://api.prosodyai.app and authenticate with the
X-API-Key header. The SDK manages this for you. Keep psk_* keys on the
server.
Server-side usage
The SDK is designed for server-side use. Create one ProsodyClient instance
and reuse it across requests.
import { ProsodyClient } from '@prosodyai/sdk';
const prosody = new ProsodyClient({
apiKey: process.env.PROSODY_API_KEY!,
});
const result = await prosody.transcribe('./call.wav');For a live call, open a realtime session and forward audio chunks from your media server:
const session = await prosody.stream.session();
session.on('turn', (turn) => {
console.log(turn.speaker.label, turn.text);
});
await session.start();
// send 1s PCM16 chunks as the call progresses
await session.send(int16Array);
await session.stop();Error handling
The SDK throws typed errors. Catch them by class to respond appropriately.
import {
ProsodyError,
AuthenticationError,
RateLimitError,
InvalidAudioError,
} from '@prosodyai/sdk';
try {
const result = await prosody.transcribe(file);
} catch (err) {
if (err instanceof AuthenticationError) {
// 401: key is invalid or expired
} else if (err instanceof RateLimitError) {
// 429: back off; err.retryAfterMs is set
} else if (err instanceof InvalidAudioError) {
// 422: file is not decodable audio
} else if (err instanceof ProsodyError) {
// any other API error
}
}| Error | HTTP | When |
|---|---|---|
AuthenticationError | 401 | Missing, invalid, or revoked key |
RateLimitError | 429 | Plan limit exceeded; retryAfterMs set |
InvalidAudioError | 422 | File is not decodable audio |
InsufficientCreditsError | 402 | Wallet is empty |
ProsodyError | — | Base class for all of the above |
Rate limits and credits
Every request consumes credits from your organization's wallet. Live sessions bill per second of audio processed; batch analysis bills per second of file duration. Check the balance and rate in the dashboard under Usage.
When the wallet is empty, requests return 402 until you top up. Rate limits
are per-key and scale with your plan; see Credits.
Webhooks
Configure a webhook endpoint under Organization → Integrations → Webhook to receive events when analysis completes. ProsodyAI signs every delivery with HMAC-SHA256 so you can verify the sender.
// app/api/webhook/route.ts
import crypto from 'crypto';
export async function POST(req: Request) {
const signature = req.headers.get('x-prosodyai-signature') ?? '';
const body = await req.text();
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET!)
.update(body)
.digest('hex');
if (signature !== expected) {
return new Response('invalid signature', { status: 401 });
}
const event = JSON.parse(body);
// event.type: 'analysis.completed' | 'session.ended'
}| Event | When | Payload |
|---|---|---|
analysis.completed | A batch transcribe call finishes | The full Transcription |
session.ended | A realtime session closes | The session's turns and speakers |
Retries: a delivery that does not return 2xx is retried with exponential
backoff for up to 24 hours, then dropped. Your endpoint must be idempotent:
the same event may arrive more than once.
Speaker identity
Within a call, every turn carries a speaker with a stable id and label,
taken from the waveform. A person your organization enrolls, with their
consent, resolves to the same identity on a later call and resumes their saved
conversation context. Enrollment and retention are governed by the
Voice Authentication Policy.
GET /v1/voice/speakers lists the people this organization has enrolled. A
display name is a label on that person: set it from the workspace People page,
or by naming them during a call.
Enrolled voice profiles stay inside the organization's namespace. The People page can rename an enrolled person or delete their profile.
const result = await prosody.transcribe('./call.wav');
for (const speaker of result.speakers) {
console.log(speaker.id, speaker.label, speaker.talkSeconds);
}