What You’ll Build
A complete deposition processing pipeline that:- Transcribes audio with speaker identification
- Stores transcripts in a searchable vault
- Generates timelines of key testimony
- Cross-references witnesses to find contradictions
- Produces formatted impeachment reports
Architecture
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:casedev vault create --name "Smith v. Jones — Depositions"
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}`);
import os
import casedev
client = casedev.Casedev(api_key=os.environ['CASEDEV_API_KEY'])
vault = client.vault.create(name='Smith v. Jones — Depositions')
print(f'Vault ID: {vault.id}')
vault, _ := client.Vault.New(ctx, casedev.VaultNewParams{
Name: casedev.F("Smith v. Jones — Depositions"),
})
fmt.Println(vault.ID)
Step 2: Upload and Transcribe a Deposition
Upload the audio file to your vault, then transcribe with speaker labels:casedev vault upload \
--id $VAULT_ID \
--filename "document.pdf" \
--content-type "application/pdf"
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;
}
import time
import requests
def transcribe_deposition(
vault_id: str,
audio_path: str,
witness_name: str,
speakers_expected: int = 3
):
filename = os.path.basename(audio_path)
# 1. Upload audio to the vault
upload = client.vault.upload(vault_id,
filename=filename,
content_type='audio/mpeg',
metadata={
'witness': witness_name,
'type': 'deposition-audio'
}
)
with open(audio_path, 'rb') as f:
requests.put(upload.upload_url, data=f,
headers={'Content-Type': 'audio/mpeg'})
# 2. Transcribe with speaker labels
job = client.voice.transcription.create(
vault_id=vault_id,
object_id=upload.object_id,
speaker_labels=True,
speakers_expected=speakers_expected,
word_boost=['plaintiff', 'defendant', 'counsel', 'objection', 'stipulate']
)
# 3. Poll until complete
result = client.voice.transcription.retrieve(job.id)
while result.status not in ('completed', 'failed'):
time.sleep(10)
result = client.voice.transcription.retrieve(job.id)
if result.status == 'failed':
raise Exception(f'Transcription failed: {result.error}')
print(f'Transcribed {witness_name}: {result.word_count} words, {round(result.audio_duration / 60)} minutes')
return result
upload, _ := client.Vault.Upload(ctx, vaultID, casedev.VaultUploadParams{
Filename: casedev.F("document.pdf"),
ContentType: casedev.F("application/pdf"),
})
// PUT file to upload.UploadURL via net/http
fmt.Println(upload.ObjectID)
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:casedev vault ingest --id $VAULT_ID --object-id $OBJECT_ID
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;
}
def index_transcript(vault_id: str, result_object_id: str, witness_name: str):
# Ingest the transcript for embedding generation + search
client.vault.ingest(result_object_id, id=vault_id)
# Poll until indexed
obj = client.vault.objects.retrieve(vault_id, result_object_id)
while obj.ingestion_status == 'processing':
time.sleep(5)
obj = client.vault.objects.retrieve(vault_id, result_object_id)
print(f'Indexed transcript for {witness_name}: {obj.ingestion_status}')
return obj
result, _ := client.Vault.Ingest(ctx, vaultID, objectID)
fmt.Println(result.Status)
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:# Process depositions sequentially
for AUDIO in depositions/*.mp3; do
WITNESS=$(basename "$AUDIO" .mp3)
echo "Processing: $WITNESS"
# Transcribe
casedev voice:transcription create \
--vault-id $VAULT_ID --file "$AUDIO" \
--speaker-labels --language en-US
# Index transcript is automatic when vault indexing is enabled
done
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;
}
def process_case(vault_id: str, depositions: list[dict]):
results = []
for depo in depositions:
print(f"\nProcessing: {depo['witness_name']} ({depo['date']})")
# Transcribe
transcription = transcribe_deposition(
vault_id,
depo['audio_path'],
depo['witness_name']
)
# Index for search
index_transcript(vault_id, transcription.result_object_id, depo['witness_name'])
results.append({
'witness': depo['witness_name'],
'date': depo['date'],
'object_id': transcription.result_object_id,
'word_count': transcription.word_count,
'duration': transcription.audio_duration
})
print(f'\nProcessed {len(results)} depositions')
return results
func processCase(ctx context.Context, client *casedev.Client, vaultID string, depositions []Deposition) []Result {
var results []Result
for _, depo := range depositions {
fmt.Printf("\nProcessing: %s (%s)\n", depo.WitnessName, depo.Date)
transcription := transcribeDeposition(ctx, client, vaultID, depo)
indexTranscript(ctx, client, vaultID, transcription.ResultObjectID, depo.WitnessName)
results = append(results, Result{
Witness: depo.WitnessName,
ObjectID: transcription.ResultObjectID,
})
}
fmt.Printf("\nProcessed %d depositions\n", len(results))
return results
}
Step 5: Generate a Testimony Timeline
Search across all depositions and build a chronological timeline of events:# Search vault for testimony, then pipe to LLM
casedev vault search --id $VAULT_ID \
--query "timeline of events" --method hybrid --limit 15
casedev llm:v1:chat create-completion \
--model anthropic/claude-sonnet-4.5 \
--message '{role: system, content: "Extract a chronological timeline from deposition testimony."}' \
--message '{role: user, content: "<testimony excerpts from search>"}' \
--temperature 0.2
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;
}
def generate_timeline(vault_id: str, topic: str):
# Search across all indexed transcripts
results = client.vault.search(vault_id,
query=f'timeline of events regarding {topic}',
method='hybrid',
top_k=20
)
chunks = '\n\n'.join(c.text for c in results.chunks)
sources = ', '.join(s.filename for s in results.sources)
# Generate structured timeline
timeline = client.llm.v1.chat.create_completion(
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': f'Topic: {topic}\n\nTestimony excerpts:\n{chunks}\n\nSources: {sources}'
}
],
temperature=0.2
)
return timeline.choices[0].message.content
// Search vault for testimony on a topic
results, _ := client.Vault.Search(ctx, vaultID, casedev.VaultSearchParams{
Query: casedev.F(topic),
Method: casedev.F(casedev.VaultSearchParamsMethodHybrid),
TopK: casedev.F(int64(15)),
})
// Build context from search results
var chunks string
for _, c := range results.Chunks {
chunks += c.Text + "\n\n"
}
// Generate timeline via LLM
timeline, _ := client.Llm.V1.Chat.NewCompletion(ctx, casedev.LlmV1ChatNewCompletionParams{
Model: casedev.F("anthropic/claude-sonnet-4.5"),
Messages: casedev.F([]casedev.LlmV1ChatNewCompletionParamsMessage{
{
Role: casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleSystem),
Content: casedev.F("You are a litigation paralegal. Extract a chronological timeline from deposition testimony."),
},
{
Role: casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleUser),
Content: casedev.F("Topic: " + topic + "\n\nTestimony excerpts:\n" + chunks),
},
}),
Temperature: casedev.F(float64(0.2)),
})
fmt.Println(timeline.Choices[0].Message.Content)
Step 6: Find Contradictions Across Witnesses
Search for a topic and compare what different witnesses said:# Search for testimony across witnesses
casedev vault search --id $VAULT_ID \
--query "accident description" --method hybrid --limit 20
# Analyze contradictions
casedev llm:v1:chat create-completion \
--model anthropic/claude-sonnet-4.5 \
--message '{role: system, content: "Identify contradictions and inconsistencies across witness testimony."}' \
--message '{role: user, content: "<grouped testimony from search>"}' \
--temperature 0.3
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;
}
def find_contradictions(vault_id: str, topic: str):
# Search across all testimony for the topic
results = client.vault.search(vault_id,
query=topic,
method='hybrid',
top_k=20
)
# Group by source file (each transcript = one witness)
by_witness = {}
for chunk in results.chunks:
witness = chunk.metadata.get('witness', chunk.filename)
if witness not in by_witness:
by_witness[witness] = []
by_witness[witness].append(chunk.text)
# Use AI to find contradictions
testimony_text = '\n'.join([
f'\n## {w}\n{chr(10).join(texts)}'
for w, texts in by_witness.items()
])
analysis = client.llm.v1.chat.create_completion(
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': f'Topic: {topic}\n\nTestimony by witness:\n{testimony_text}'
}
],
temperature=0.3
)
return analysis.choices[0].message.content
// Search across all testimony for the topic
results, _ := client.Vault.Search(ctx, vaultID, casedev.VaultSearchParams{
Query: casedev.F(topic),
Method: casedev.F(casedev.VaultSearchParamsMethodHybrid),
TopK: casedev.F(int64(20)),
})
// Group testimony by witness and build context
var testimonyText string
for _, chunk := range results.Chunks {
testimonyText += fmt.Sprintf("## %s\n%s\n\n", chunk.Filename, chunk.Text)
}
// Analyze contradictions via LLM
analysis, _ := client.Llm.V1.Chat.NewCompletion(ctx, casedev.LlmV1ChatNewCompletionParams{
Model: casedev.F("anthropic/claude-sonnet-4.5"),
Messages: casedev.F([]casedev.LlmV1ChatNewCompletionParamsMessage{
{
Role: casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleSystem),
Content: casedev.F("You are a litigation attorney. Identify contradictions, inconsistencies, and corroborations across witness testimony."),
},
{
Role: casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleUser),
Content: casedev.F("Topic: " + topic + "\n\nTestimony by witness:\n" + testimonyText),
},
}),
Temperature: casedev.F(float64(0.3)),
})
fmt.Println(analysis.Choices[0].Message.Content)
Step 7: Generate a PDF Impeachment Report
Combine the timeline and contradiction analysis into a formatted report:casedev format:v1 document \
--content "# Report" \
--input-format md --output-format pdf
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;
}
from datetime import date
def generate_impeachment_report(vault_id: str, case_name: str, topics: list[str]):
report_content = f'# Impeachment Report\n\n**Case:** {case_name}\n**Generated:** {date.today().isoformat()}\n\n---\n\n'
# Generate timeline
report_content += '## Chronological Timeline\n\n'
timeline = generate_timeline(vault_id, case_name)
report_content += timeline + '\n\n---\n\n'
# Analyze each topic
report_content += '## Contradiction Analysis\n\n'
for topic in topics:
report_content += f'### {topic}\n\n'
contradictions = find_contradictions(vault_id, topic)
report_content += contradictions + '\n\n'
# Convert to PDF
report = client.format.v1.create_document(
content=report_content,
input_format='md',
output_format='pdf',
options={
'template': 'standard',
'header': f'CONFIDENTIAL — {case_name} — 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
result, _ := client.Format.V1.NewDocument(ctx, casedev.FormatV1NewDocumentParams{
Content: casedev.F("# Report\n\nContent here..."),
InputFormat: casedev.F("md"),
OutputFormat: casedev.F("pdf"),
})
// result is *http.Response with PDF body
Complete Example
Putting it all together:casedev vault create --name "Smith v. Jones — Depositions"
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();
import os
import casedev
client = casedev.Casedev(api_key=os.environ['CASEDEV_API_KEY'])
def main():
# Create a vault for the case
vault = client.vault.create(name='Smith v. Jones — Depositions')
# Process depositions
process_case(vault.id, [
{
'audio_path': 'depositions/ceo-johnson.m4a',
'witness_name': 'CEO Johnson',
'date': '2024-03-15'
},
{
'audio_path': 'depositions/cto-smith.m4a',
'witness_name': 'CTO Smith',
'date': '2024-03-18'
},
{
'audio_path': 'depositions/it-director-williams.m4a',
'witness_name': 'IT Director Williams',
'date': '2024-03-20'
}
])
# Generate impeachment report
report = generate_impeachment_report(
vault.id,
'Smith v. Jones',
[
'When the CEO learned of the data breach',
'Who authorized the system shutdown',
'Timeline of security notifications'
]
)
print('Report generated successfully')
main()
vault, _ := client.Vault.New(ctx, casedev.VaultNewParams{
Name: casedev.F("Smith v. Jones — Depositions"),
})
fmt.Println(vault.ID)
Example Contradiction Report
### 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
# CLI displays errors to stderr with status codes
casedev voice:transcription create --vault-id $VAULT_ID --object-id $AUDIO_OBJECT_ID
# Error: 413 Payload Too Large — split into segments
# Error: 429 Too Many Requests — retry after a delay
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);
}
}
try:
result = transcribe_deposition(vault_id, audio_path, witness)
except casedev.APIError:
print('Audio file too large — split into segments')
except casedev.RateLimitError:
print('Rate limited — retry after a delay')
except casedev.APIError as e:
print(f'Transcription failed: {e.message}')
result, err := transcribeDeposition(ctx, client, vaultID, audio, filename, witness)
if err != nil {
var apiErr *casedev.Error
if errors.As(err, &apiErr) {
switch apiErr.StatusCode {
case 413:
fmt.Println("Audio file too large — split into segments")
case 429:
fmt.Println("Rate limited — retry after a delay")
default:
fmt.Printf("Transcription failed: %s\n", apiErr.Message)
}
}
}
Use Webhooks in Production
Instead of polling, subscribe to events for transcription and ingestion completion:# Subscribe to vault events
casedev vault:events:subscriptions create \
--id $VAULT_ID \
--callback-url "https://your-app.com/webhooks/vault" \
--event-type object.ingested \
--event-type object.failed
# Subscribe to transcription events
# Your webhook handler receives status updates automatically
// 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
# Subscribe to vault events
client.vault.events.subscriptions.create(vault_id,
callback_url='https://your-app.com/webhooks/vault',
event_types=['object.ingested', 'object.failed']
)
# Subscribe to transcription events
# Your webhook handler receives status updates automatically
// Subscribe to vault events
client.Vault.Events.Subscriptions.New(ctx, vaultID, casedev.VaultEventSubscriptionNewParams{
CallbackURL: casedev.F("https://your-app.com/webhooks/vault"),
EventTypes: casedev.F([]string{"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
- Voice Transcription Reference — Speaker labels, language options, and more
- Vault Search Reference — Advanced search methods and filtering
- Format Service — PDF and DOCX generation
- Discovery Pipeline — Batch document processing

