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
  }
}
ErrorHTTPWhen
AuthenticationError401Missing, invalid, or revoked key
RateLimitError429Plan limit exceeded; retryAfterMs set
InvalidAudioError422File is not decodable audio
InsufficientCreditsError402Wallet 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'
}
EventWhenPayload
analysis.completedA batch transcribe call finishesThe full Transcription
session.endedA realtime session closesThe 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);
}

On this page