What You’ll Build
A script that:
Searches your indexed documents in a vault
Analyzes the results with an LLM
Returns structured insights with source citations
Time to complete: 15 minutes
Architecture
│ Your App │ ──▶ │ Vault Search │ ──▶ │ LLM │
│ │ │ (Hybrid) │ │ Analysis │
│ vaultId │ │ 10 chunks │ │ Structured │
│ query │ │ + sources │ │ response │
└──────────────┘ └──────────────┘ └──────────────┘
Prerequisites
Case.dev API key (get one here )
A vault with ingested documents (we’ll set one up if you don’t have one)
Step 1: Set Up Your Vault
If you already have a vault with indexed documents, skip to Step 2.
Create a vault
TypeScript
Python
C#
Java
PHP
cURL
Go
CLI
import Casedev from 'casedev' ;
const client = new Casedev ({ apiKey : process . env . CASEDEV_API_KEY });
const vault = await client . vault . create ({
name : 'Legal Research Vault'
});
console . log ( `Vault ID: ${ vault . id } ` );
Save the returned id — you’ll need it.
Upload a document
TypeScript
Python
C#
Java
PHP
cURL
Go
CLI
// Get upload URL
const upload = await client . vault . upload ( vault . id , {
filename : 'contract.pdf' ,
contentType : 'application/pdf'
});
// Upload the file directly to S3
await fetch ( upload . uploadUrl , {
method : 'PUT' ,
headers : { 'Content-Type' : 'application/pdf' },
body : fs . readFileSync ( 'contract.pdf' )
});
console . log ( `Object ID: ${ upload . objectId } ` );
Ingest (index) the document
TypeScript
Python
C#
Java
PHP
cURL
Go
CLI
await client . vault . ingest ( vault . id , upload . objectId );
// Poll until complete
let obj = await client . vault . objects . retrieve ( vault . id , upload . objectId );
while ( obj . ingestionStatus === 'processing' ) {
await new Promise ( r => setTimeout ( r , 5000 ));
obj = await client . vault . objects . retrieve ( vault . id , upload . objectId );
}
console . log ( `Ingestion: ${ obj . ingestionStatus } ` );
Ingestion is async. Wait for ingestionStatus: "completed" before searching. For production, use webhooks instead of polling.
Step 2: Search Your Documents
Query your vault with a natural language question:
TypeScript
Python
C#
Java
PHP
cURL
Go
CLI
const searchResults = await client . vault . search ( vaultId , {
query : 'What are the key terms of this agreement?' ,
method : 'hybrid' ,
limit : 10
});
console . log ( `Found ${ searchResults . chunks . length } relevant passages` );
{
"chunks" : [
{
"text" : "The Parties agree to the following terms..." ,
"object_id" : "obj_xyz789" ,
"hybridScore" : 0.89 ,
"vectorScore" : 0.92 ,
"bm25Score" : 0.78
}
],
"sources" : [
{ "id" : "obj_xyz789" , "filename" : "contract.pdf" }
]
}
Step 3: Analyze with an LLM
Pass the search results to an LLM for structured analysis:
TypeScript
Python
C#
Java
PHP
Go
CLI
const chunks = searchResults . chunks . map ( c => c . text ). join ( ' \n\n ' );
const sources = searchResults . sources . map ( s => s . filename ). join ( ', ' );
const analysis = await client . llm . v1 . chat . createCompletion ({
model : 'anthropic/claude-sonnet-4.5' ,
messages : [
{
role : 'system' ,
content : 'You are a legal document analyst. Analyze the provided document excerpts and answer the user \' s question. Always cite specific passages to support your analysis.'
},
{
role : 'user' ,
content : `## Document Excerpts \n\n ${ chunks } \n\n ## Sources \n ${ sources } \n\n ## Question \n What are the key terms of this agreement?`
}
],
temperature : 0.3
});
console . log ( analysis . choices [ 0 ]. message . content );
{
"choices" : [{
"message" : {
"content" : "Based on the contract excerpts, the key terms include: \n\n 1. **Payment Terms**: Section 3.2 states that payment is due within 30 days... \n\n 2. **Termination**: Either party may terminate with 90 days written notice (Section 7.1)... \n\n 3. **Liability Cap**: Liability is limited to the total fees paid in the preceding 12 months (Section 9.3)..."
}
}],
"usage" : {
"prompt_tokens" : 1245 ,
"completion_tokens" : 387 ,
"total_tokens" : 1632 ,
"cost" : 0.004896
}
}
Complete Example
Putting it all together — a reusable function that searches and analyzes:
TypeScript
Python
C#
Java
PHP
Go
CLI
import Casedev from 'casedev' ;
const client = new Casedev ({ apiKey : process . env . CASEDEV_API_KEY });
async function analyzeDocuments ( vaultId : string , query : string ) {
// 1. Search
const searchResults = await client . vault . search ( vaultId , {
query ,
method : 'hybrid' ,
limit : 10
});
const chunks = searchResults . chunks . map ( c => c . text ). join ( ' \n\n ' );
const sources = searchResults . sources . map ( s => s . filename ). join ( ', ' );
// 2. Analyze
const analysis = await client . llm . v1 . chat . createCompletion ({
model : 'anthropic/claude-sonnet-4.5' ,
messages : [
{
role : 'system' ,
content : `You are a senior legal analyst. Provide comprehensive analysis with:
1. Executive Summary
2. Key Findings (cite specific passages)
3. Supporting Evidence
4. Recommendations`
},
{
role : 'user' ,
content : `## Document Excerpts \n\n ${ chunks } \n\n ## Sources \n ${ sources } \n\n ## Question \n ${ query } `
}
],
temperature : 0.3
});
return {
answer : analysis . choices [ 0 ]. message . content ,
sources : searchResults . sources ,
usage : analysis . usage
};
}
// Run it
const result = await analyzeDocuments ( 'vault_abc123' , 'What are the indemnification clauses?' );
console . log ( result . answer );
Extending the Analyzer
Run a second LLM pass to extract structured entities:
TypeScript
Python
C#
Java
PHP
Go
CLI
const entities = await client . llm . v1 . chat . createCompletion ({
model : 'openai/gpt-4o' ,
messages : [
{
role : 'system' ,
content : 'Extract named entities as JSON: {people: [], organizations: [], dates: [], locations: [], monetary_amounts: []}'
},
{ role : 'user' , content : chunks }
],
temperature : 0
});
Generate a PDF Report
Convert the analysis into a formatted document:
TypeScript
Python
C#
Java
PHP
Go
CLI
const report = await client . format . v1 . document ({
content : `# Legal Analysis Report \n\n **Query:** ${ query } \n\n ${ analysis . choices [ 0 ]. message . content } ` ,
input_format : 'md' ,
output_format : 'pdf'
});
Production Tips
Error Handling
TypeScript
Python
Go
CLI
C#
Java
PHP
try {
const result = await analyzeDocuments ( vaultId , query );
console . log ( result . answer );
} catch ( error ) {
if ( error . status === 404 ) {
console . error ( 'Vault not found — check your vault ID' );
} else if ( error . status === 429 ) {
console . error ( 'Rate limited — retry after a delay' );
} else {
console . error ( 'Analysis failed:' , error . message );
}
}
Use temperature: 0 for factual extraction tasks. Try cheaper models like deepseek/deepseek-chat for simpler analysis.
Next Steps