Skip to main content

What You’ll Build

A complete deposition processing pipeline that:
  1. Transcribes audio with speaker identification
  2. Stores transcripts in a searchable vault
  3. Generates timelines of key testimony
  4. Cross-references witnesses to find contradictions
  5. Produces formatted impeachment reports
Time to complete: 30 minutes

Architecture

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  Audio Files │ ──▶ │    Voice     │ ──▶ │    Vault     │
│  (per witness)│     │ Transcription│     │   Storage    │
└──────────────┘     │ + Speakers   │     │  + Indexing  │
                     └──────────────┘     └──────┬───────┘

┌──────────────┐     ┌──────────────┐     ┌──────▼───────┐
│  PDF Report  │ ◀── │   LLM        │ ◀── │  Vault       │
│  (Format)    │     │   Analysis   │     │  Search      │
└──────────────┘     └──────────────┘     └──────────────┘

Prerequisites

  • Case.dev API key (get one here)
  • A vault for storing transcripts
  • Deposition audio files (MP3, M4A, WAV, etc.)

Step 1: Set Up Your Vault

Create a vault to hold all deposition transcripts for a case:
import Casedev from 'casedev';

const client = new Casedev({ apiKey: process.env.CASEDEV_API_KEY });

const vault = await client.vault.create({
  name: 'Smith v. Jones — Depositions'
});

console.log(`Vault ID: ${vault.id}`);

Step 2: Upload and Transcribe a Deposition

Upload the audio file to your vault, then transcribe with speaker labels:
async function transcribeDeposition(
  vaultId: string,
  audioFile: Buffer,
  filename: string,
  witnessName: string,
  speakersExpected: number = 3 // Witness, attorney, opposing counsel
) {
  // 1. Upload audio to the vault
  const upload = await client.vault.upload(vaultId, {
    filename,
    contentType: 'audio/mpeg',
    metadata: {
      witness: witnessName,
      type: 'deposition-audio'
    }
  });

  await fetch(upload.uploadUrl, {
    method: 'PUT',
    headers: { 'Content-Type': 'audio/mpeg' },
    body: audioFile
  });

  // 2. Transcribe with speaker labels
  const job = await client.voice.transcription.create({
    vault_id: vaultId,
    object_id: upload.objectId,
    speaker_labels: true,
    speakers_expected: speakersExpected,
    word_boost: ['plaintiff', 'defendant', 'counsel', 'objection', 'stipulate']
  });

  // 3. Poll until complete
  let result = await client.voice.transcription.retrieve(job.id);
  while (result.status !== 'completed' && result.status !== 'failed') {
    await new Promise(r => setTimeout(r, 10000));
    result = await client.voice.transcription.retrieve(job.id);
  }

  if (result.status === 'failed') {
    throw new Error(`Transcription failed: ${result.error}`);
  }

  console.log(`Transcribed ${witnessName}: ${result.word_count} words, ${Math.round(result.audio_duration / 60)} minutes`);

  return result;
}
Speaker labels are automatic. The transcription service identifies speakers by voice characteristics. Set speakers_expected for better accuracy when you know the number of participants.

Step 3: Store Transcript in the Vault

The vault-based transcription automatically stores the transcript. Now ingest it for semantic search:
async function indexTranscript(
  vaultId: string,
  resultObjectId: string,
  witnessName: string
) {
  // Ingest the transcript for embedding generation + search
  await client.vault.ingest(vaultId, resultObjectId);

  // Poll until indexed
  let obj = await client.vault.objects.retrieve(vaultId, resultObjectId);
  while (obj.ingestionStatus === 'processing') {
    await new Promise(r => setTimeout(r, 5000));
    obj = await client.vault.objects.retrieve(vaultId, resultObjectId);
  }

  console.log(`Indexed transcript for ${witnessName}: ${obj.ingestionStatus}`);
  return obj;
}
For production, use webhooks instead of polling for both transcription and ingestion status changes.

Step 4: Batch Process Multiple Depositions

Process all depositions for a case in sequence:
interface Deposition {
  audioFile: Buffer;
  filename: string;
  witnessName: string;
  date: string;
}

async function processCase(vaultId: string, depositions: Deposition[]) {
  const results = [];

  for (const depo of depositions) {
    console.log(`\nProcessing: ${depo.witnessName} (${depo.date})`);

    // Transcribe
    const transcription = await transcribeDeposition(
      vaultId,
      depo.audioFile,
      depo.filename,
      depo.witnessName
    );

    // Index for search
    await indexTranscript(vaultId, transcription.result_object_id, depo.witnessName);

    results.push({
      witness: depo.witnessName,
      date: depo.date,
      objectId: transcription.result_object_id,
      wordCount: transcription.word_count,
      duration: transcription.audio_duration
    });
  }

  console.log(`\nProcessed ${results.length} depositions`);
  return results;
}

Step 5: Generate a Testimony Timeline

Search across all depositions and build a chronological timeline of events:
async function generateTimeline(vaultId: string, topic: string) {
  // Search across all indexed transcripts
  const results = await client.vault.search(vaultId, {
    query: `timeline of events regarding ${topic}`,
    method: 'hybrid',
    limit: 20
  });

  const chunks = results.chunks.map(c => c.text).join('\n\n');
  const sources = results.sources.map(s => s.filename).join(', ');

  // Generate structured timeline
  const timeline = await client.llm.v1.chat.createCompletion({
    model: 'anthropic/claude-sonnet-4.5',
    messages: [
      {
        role: 'system',
        content: `You are a litigation paralegal. Create a chronological timeline from deposition testimony.

For each event:
- Date (exact or approximate)
- What happened
- Who testified about it (witness name)
- Direct quote from testimony
- Page/line reference if available

Format as a markdown table sorted by date.`
      },
      {
        role: 'user',
        content: `Topic: ${topic}\n\nTestimony excerpts:\n${chunks}\n\nSources: ${sources}`
      }
    ],
    temperature: 0.2
  });

  return timeline.choices[0].message.content;
}

Step 6: Find Contradictions Across Witnesses

Search for a topic and compare what different witnesses said:
async function findContradictions(vaultId: string, topic: string) {
  // Search across all testimony for the topic
  const results = await client.vault.search(vaultId, {
    query: topic,
    method: 'hybrid',
    limit: 20
  });

  // Group by source file (each transcript = one witness)
  const byWitness: Record<string, string[]> = {};
  for (const chunk of results.chunks) {
    const witness = chunk.metadata?.witness || chunk.filename;
    if (!byWitness[witness]) byWitness[witness] = [];
    byWitness[witness].push(chunk.text);
  }

  // Use AI to find contradictions
  const analysis = await client.llm.v1.chat.createCompletion({
    model: 'anthropic/claude-sonnet-4.5',
    messages: [
      {
        role: 'system',
        content: `You are a litigation attorney preparing for cross-examination.
        
Analyze testimony from multiple witnesses and identify:
1. Direct contradictions (conflicting facts)
2. Inconsistencies (different emphasis or omissions)
3. Corroborations (matching testimony across witnesses)

For each finding, cite the specific quote and witness name.
Rate each contradiction as HIGH, MEDIUM, or LOW significance.`
      },
      {
        role: 'user',
        content: `Topic: ${topic}\n\nTestimony by witness:\n${
          Object.entries(byWitness)
            .map(([w, texts]) => `\n## ${w}\n${texts.join('\n')}`)
            .join('\n')
        }`
      }
    ],
    temperature: 0.3
  });

  return analysis.choices[0].message.content;
}

Step 7: Generate a PDF Impeachment Report

Combine the timeline and contradiction analysis into a formatted report:
async function generateImpeachmentReport(
  vaultId: string,
  caseName: string,
  topics: string[]
) {
  let reportContent = `# Impeachment Report\n\n**Case:** ${caseName}\n**Generated:** ${new Date().toLocaleDateString()}\n\n---\n\n`;

  // Generate timeline
  reportContent += `## Chronological Timeline\n\n`;
  const timeline = await generateTimeline(vaultId, caseName);
  reportContent += timeline + '\n\n---\n\n';

  // Analyze each topic
  reportContent += `## Contradiction Analysis\n\n`;
  for (const topic of topics) {
    reportContent += `### ${topic}\n\n`;
    const contradictions = await findContradictions(vaultId, topic);
    reportContent += contradictions + '\n\n';
  }

  // Convert to PDF
  const report = await client.format.v1.document({
    content: reportContent,
    input_format: 'md',
    output_format: 'pdf',
    options: {
      template: 'standard',
      header: `CONFIDENTIAL — ${caseName} — Impeachment Report`,
      footer: 'Page {{page}} of {{pages}}',
      styles: {
        h1: { font: 'Times New Roman', size: 16, bold: true, alignment: 'center' },
        h2: { font: 'Times New Roman', size: 14, bold: true },
        h3: { font: 'Times New Roman', size: 12, bold: true },
        p: { font: 'Times New Roman', size: 11, spacingAfter: 8 }
      }
    }
  });

  return report;
}

Complete Example

Putting it all together:
import Casedev from 'casedev';
import fs from 'fs';

const client = new Casedev({ apiKey: process.env.CASEDEV_API_KEY });

async function main() {
  // Create a vault for the case
  const vault = await client.vault.create({
    name: 'Smith v. Jones — Depositions'
  });

  // Process depositions
  await processCase(vault.id, [
    {
      audioFile: fs.readFileSync('depositions/ceo-johnson.m4a'),
      filename: 'ceo-johnson.m4a',
      witnessName: 'CEO Johnson',
      date: '2024-03-15'
    },
    {
      audioFile: fs.readFileSync('depositions/cto-smith.m4a'),
      filename: 'cto-smith.m4a',
      witnessName: 'CTO Smith',
      date: '2024-03-18'
    },
    {
      audioFile: fs.readFileSync('depositions/it-director-williams.m4a'),
      filename: 'it-director-williams.m4a',
      witnessName: 'IT Director Williams',
      date: '2024-03-20'
    }
  ]);

  // Generate impeachment report
  const report = await generateImpeachmentReport(
    vault.id,
    'Smith v. Jones',
    [
      'When the CEO learned of the data breach',
      'Who authorized the system shutdown',
      'Timeline of security notifications'
    ]
  );

  console.log('Report generated successfully');
}

main();

Example Contradiction Report

## CONTRADICTION IDENTIFIED — HIGH SIGNIFICANCE

### Topic: When the CEO learned of the data breach

**CEO Johnson (Deposition, March 15, 2024):**
> "I was not informed of any security incident until July 15th,
> when our IT director called me at home."

**CTO Smith (Deposition, March 18, 2024):**
> "I sent an urgent email to the entire executive team, including
> the CEO, on July 12th explicitly detailing the intrusion and
> recommending immediate action."

**IT Director Williams (Deposition, March 20, 2024):**
> "The CTO instructed me to brief the CEO on July 12th. I called
> his office but was told he was unavailable. I left a detailed
> voicemail."

### Analysis
The CEO claims no knowledge until July 15th, but both the CTO and
IT Director indicate notification attempts on July 12th — three days
earlier. The CTO claims direct email notification, and the IT
Director confirms a voicemail was left.

### Impeachment Strategy
1. Introduce July 12th email as Exhibit A
2. Subpoena CEO's voicemail records for July 12th
3. Confront CEO with CTO and IT Director testimony

Production Tips

Error Handling

try {
  const result = await transcribeDeposition(vaultId, audio, filename, witness);
} catch (error) {
  if (error.status === 413) {
    console.error('Audio file too large — split into segments');
  } else if (error.status === 429) {
    console.error('Rate limited — retry after a delay');
  } else {
    console.error('Transcription failed:', error.message);
  }
}

Use Webhooks in Production

Instead of polling, subscribe to events for transcription and ingestion completion:
// Subscribe to vault events
await client.vault.events.subscriptions.create(vaultId, {
  url: 'https://your-app.com/webhooks/vault',
  events: ['object.ingested', 'object.failed']
});

// Subscribe to transcription events
// Your webhook handler receives status updates automatically
For long depositions (2+ hours), transcription may take several minutes. Webhooks prevent wasted polling requests.

Next Steps