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). Billable duration uses fractional minutes (seconds ÷ 60), not rounded-up whole minutes per session. 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. Duration past included + bank bills at 1 credit per minuteon paid tiers (fractional — e.g. ~72 s ≈ 1.2 credits, not a rounded-up whole minute). 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.

Project members with write access can delete a session recording from the session detail page (Delete recording) or via the API. Deletion removes the stored audio object and the database row; it does not refund included or metered recording minutes.

Stereo channel layout

Session recordings are stereo at 48 kHz. The left channel is the agent voice (TTS playback). The right channel is the caller (user microphone). This layout is useful when reviewing on headphones or in a DAW; mono speakers sum both channels together.

Delete a recording

  • Dashboard: Project → Sessions → session detail → Delete recording (confirm dialog).
  • API: DELETE /projects/:projectId/sessions/:orchestratorSessionId/recording — returns 204 when the recording row and storage object are removed. Project scope is resolved from your API key or dashboard session (not a client-supplied project id alone).
  • CLI: voicethere sessions recording delete <sessionId> (planned — use the API or dashboard until the CLI command ships).

Agent recording controls

When the project setting is on and the session connects, the runner begins capturing audio automatically. Use @voicethere/agent helpers with the session id to pause, resume, or stop capture — the runner owns the file. Typical reasons to call them:

  • Consent — if the caller declines recording, call stopRecording immediately (capture already started when the session connected). startRecording / resumeRecording restarts after a pause or stop.
  • 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 capture or ingest audio. 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,
  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 no if you do not want recording.",
    );
  },
  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.");
    }
  },
});

Pause while collecting sensitive information

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

export default defineAgent({
  async onSessionStart({ sessionId, recordingAvailable }) {
    if (!recordingAvailable) return;
    // Runner already recording when the project setting is on.
  },
  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