VoiceThere

Conversation recording

When enabled for a project, VoiceThere stores voice-session audio (user microphone + agent TTS playback) for dashboard playback on the session detail page. Recording is opt in— default off — and scoped to the authenticated project's settings and subscription. Cross-project access is denied.

Where to configure

  • Dashboard: Project overview → Session settings → Conversation recording (conversation_recording_enabled) and Metered recording overage (conversation_recording_metered_overage_enabled).
  • CLI: voicethere projects session-settings list / voicethere projects session-settings set conversation_recording_enabled <true|false>
  • API: PATCH /projects/:projectId/session-settings — see Control plane API.

Defaults: conversation_recording_enabled is false; conversation_recording_metered_overage_enabled is false. Settings apply on the next Deploy to cloud.

Included minutes & overage

Each subscription tier includes a monthly pool of recorded minutes (UTC calendar month). Partial minutes bill as one minute. Retention follows your plan.

PlanIncluded min / monthRetention
Free607 days
Budget12014 days
Budget+24014 days
Advanced50031 days
Ultimate1,00031 days

Rollover vs metered overage

With metered recording overage off (default), unused included minutes at the UTC month boundary roll into a capped minute bank(bank size capped at one month's included allowance). Recording stops when included + bank are exhausted.

With metered recording overage on, unused minutes do not roll over. Minutes past included + bank bill at 1 credit per minute on paid tiers (Free cannot use metered recording overage). Metered overage requires org billing readiness — a payment method on file and effective metered toggles — same gate as session billable minutes. See Billing & usage credits.

Dashboard playback

Open Project → Sessions → session detail. When recording was enabled for the project at session time, an audio player loads a signed URL for the stored session mix. If recording is disabled or no audio was captured, the panel explains how to enable recording on the project.

Agent recording controls

Even with the project setting on, your agent decides when audio is written. Use @voicethere/agent helpers with the session id — the runner owns capture. Typical reasons to call them:

  • Consent — only startRecording after the caller agrees; if they decline, never start (or stopRecording immediately).
  • Sensitive input — when the caller is about to share a card number, SSN, password, or other PII, pauseRecording so those frames are omitted from the file; resumeRecording afterward.
  • End of capture stopRecording when the recorded part of the call is finished (session end or mid-call).

Project setting conversation_recording_enabled must be true (and deployed) or the runner will not ingest audio even if you call startRecording. Check ctx.recordingAvailable on onSessionStart before prompting for consent. Plans still need remaining included, bank, or metered allowance.

Recording helpers return a promise that resolves when the runner acknowledges the control message. Local verify runs without a runner parent resolve immediately with reason: "local_mock" so laptop testing never blocks on IPC.

Skip recording when the customer declines

import {
  defineAgent,
  speak,
  startRecording,
  stopRecording,
} from "@voicethere/agent";

export default defineAgent({
  async onSessionStart({ sessionId, recordingAvailable }) {
    if (!recordingAvailable) return;
    await speak(
      sessionId,
      "This call may be recorded for quality. Say yes to allow recording, or no to continue without it.",
    );
  },
  async onUserSpeechFinal({ sessionId, text }) {
    const answer = text.trim().toLowerCase();
    if (/\bno\b|do not|don't|decline/.test(answer)) {
      const result = await stopRecording(sessionId);
      if (!result.ok) return;
      await speak(sessionId, "Understood — we will not record this call.");
      return;
    }
    if (/\byes\b|ok|okay|agree|allow/.test(answer)) {
      const result = await startRecording(sessionId);
      if (!result.ok) return;
      await speak(sessionId, "Thanks — recording is on.");
    }
  },
});

Pause while collecting sensitive information

import {
  defineAgent,
  pauseRecording,
  resumeRecording,
  speak,
  startRecording,
  stopRecording,
} from "@voicethere/agent";

export default defineAgent({
  async onSessionStart({ sessionId, recordingAvailable }) {
    if (!recordingAvailable) return;
    const result = await startRecording(sessionId);
    if (!result.ok) return;
  },
  async onUserSpeechFinal({ sessionId, text }) {
    // About to collect payment details — omit audio until done
    if (/credit card|card number|cvv|social security|password/i.test(text)) {
      const pause = await pauseRecording(sessionId);
      if (!pause.ok) return;
      await speak(
        sessionId,
        "I will pause recording while you share that. Say continue when you are done.",
      );
      return;
    }
    if (/\bcontinue\b|done|finished/i.test(text)) {
      await resumeRecording(sessionId);
    }
  },
  async onSessionEnd({ sessionId }) {
    await stopRecording(sessionId);
  },
});

Multi-tenant isolation

Recordings, minute banks, and playback URLs are keyed to the projectthat owns the session. API routes resolve the project from your API key or dashboard session — never from a client-supplied project id alone. One project cannot stream or bill against another project's recording allowance.

CLI examples

# Enable recording (default off)
voicethere projects session-settings set conversation_recording_enabled true
voicethere deploy --wait

# Rollover mode (default) — bank unused minutes at month end
voicethere projects session-settings set conversation_recording_metered_overage_enabled false

# Metered overage — 1 credit/min past included + bank (paid tiers + billing ready)
voicethere projects session-settings set conversation_recording_metered_overage_enabled true

# Download a session recording (waits until ready, writes Opus file)
voicethere sessions recording <orchestratorSessionId> --wait --output ./session.opus

# Metadata only (status, duration_ms, …)
voicethere sessions recording <orchestratorSessionId> --json

Related

← All documentation