> ## Documentation Index
> Fetch the complete documentation index at: https://docs.case.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Document Analyzer

> Search documents and generate AI-powered analysis with vault search and LLM

## What You'll Build

A script that:

1. Searches your indexed documents in a vault
2. Analyzes the results with an LLM
3. Returns structured insights with source citations

**Time to complete:** 15 minutes

## Architecture

```mermaid theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
flowchart LR
    A["Your App<br/>vaultId + query"] --> B["Vault Search<br/>Hybrid · 10 chunks + sources"]
    B --> C["LLM Analysis<br/>Structured response"]
```

## Prerequisites

* Case.dev API key ([get one here](https://console.case.dev))
* 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

<CodeGroup>
  ```bash title="cURL" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  curl -X POST https://api.case.dev/vault \
    -H "Authorization: Bearer $CASEDEV_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"name": "Legal Research Vault"}'
  ```

  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  casedev vault create --name "Legal Research Vault"
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  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}`);
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import os
  import casedev

  client = casedev.Casedev(api_key=os.environ['CASEDEV_API_KEY'])

  vault = client.vault.create(name='Legal Research Vault')

  print(f'Vault ID: {vault.id}')
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  vault, _ := client.Vault.New(ctx, casedev.VaultNewParams{
  	Name: casedev.F("Legal Research Vault"),
  })
  fmt.Println(vault.ID)
  ```
</CodeGroup>

Save the returned `id` — you'll need it.

### Upload a document

<CodeGroup>
  ```bash title="cURL" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  # Get upload URL
  UPLOAD_RESPONSE=$(curl -s -X POST "https://api.case.dev/vault/$VAULT_ID/upload" \
    -H "Authorization: Bearer $CASEDEV_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"filename": "contract.pdf", "contentType": "application/pdf"}')

  UPLOAD_URL=$(echo $UPLOAD_RESPONSE | jq -r '.uploadUrl')
  OBJECT_ID=$(echo $UPLOAD_RESPONSE | jq -r '.objectId')

  # Upload your file
  curl -X PUT "$UPLOAD_URL" \
    -H "Content-Type: application/pdf" \
    --data-binary @contract.pdf
  ```

  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  casedev vault upload \
    --id $VAULT_ID \
    --filename "document.pdf" \
    --content-type "application/pdf"
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  // 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}`);
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import requests

  # Get upload URL
  upload = client.vault.upload(vault.id,
      filename='contract.pdf',
      content_type='application/pdf'
  )

  # Upload the file directly to S3
  with open('contract.pdf', 'rb') as f:
      requests.put(upload.upload_url, data=f,
          headers={'Content-Type': 'application/pdf'})

  print(f'Object ID: {upload.object_id}')
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  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)
  ```
</CodeGroup>

### Ingest (index) the document

<CodeGroup>
  ```bash title="cURL" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  curl -X POST "https://api.case.dev/vault/$VAULT_ID/ingest/$OBJECT_ID" \
    -H "Authorization: Bearer $CASEDEV_API_KEY"

  # Check status (poll until completed)
  curl "https://api.case.dev/vault/$VAULT_ID/objects/$OBJECT_ID" \
    -H "Authorization: Bearer $CASEDEV_API_KEY" | jq '.ingestionStatus'
  ```

  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  casedev vault ingest --id $VAULT_ID --object-id $OBJECT_ID
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  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}`);
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import time

  client.vault.ingest(upload.object_id, id=vault.id)

  # Poll until complete
  obj = client.vault.objects.retrieve(vault.id, upload.object_id)
  while obj.ingestion_status == 'processing':
      time.sleep(5)
      obj = client.vault.objects.retrieve(vault.id, upload.object_id)

  print(f'Ingestion: {obj.ingestion_status}')
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  result, _ := client.Vault.Ingest(ctx, objectID, casedev.VaultIngestParams{
  	ID: casedev.F(vaultID),
  })
  fmt.Println(result.Status)
  ```
</CodeGroup>

<Info>
  **Ingestion is async.** Wait for `ingestionStatus: "completed"` before searching. For production, use [webhooks](/vault/webhooks) instead of polling.
</Info>

## Step 2: Search Your Documents

Query your vault with a natural language question:

<CodeGroup>
  ```bash title="cURL" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  curl -X POST "https://api.case.dev/vault/$VAULT_ID/search" \
    -H "Authorization: Bearer $CASEDEV_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "What are the key terms of this agreement?",
      "method": "hybrid",
      "limit": 10
    }'
  ```

  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  casedev vault search \
    --id $VAULT_ID \
    --query "What are the key terms of this agreement?"
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  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`);
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  search_results = client.vault.search(vault_id,
      query='What are the key terms of this agreement?',
      method='hybrid',
      top_k=10
  )

  print(f'Found {len(search_results.chunks)} relevant passages')
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  results, _ := client.Vault.Search(ctx, vaultID, casedev.VaultSearchParams{
  	Query: casedev.F("What are the key terms of this agreement?"),
  	Method: casedev.F(casedev.VaultSearchParamsMethodHybrid),
  })
  for _, chunk := range results.Chunks {
  	fmt.Println(chunk.Text)
  }
  ```
</CodeGroup>

```json title="Response" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
{
  "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:

<CodeGroup>
  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  casedev llm:v1:chat create-completion \
    --model anthropic/claude-sonnet-4.5 \
    --message '{role: system, content: "You are a legal document analyst. Cite specific passages to support your analysis."}' \
    --message '{role: user, content: "<document excerpts>\n\nQuestion: What are the key terms of this agreement?"}' \
    --temperature 0.3
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  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\nWhat are the key terms of this agreement?`
      }
    ],
    temperature: 0.3
  });

  console.log(analysis.choices[0].message.content);
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  chunks = '\n\n'.join(c.text for c in search_results.chunks)
  sources = ', '.join(s.filename for s in search_results.sources)

  analysis = client.llm.v1.chat.create_completion(
      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': f'## Document Excerpts\n\n{chunks}\n\n## Sources\n{sources}\n\n## Question\nWhat are the key terms of this agreement?'
          }
      ],
      temperature=0.3
  )

  print(analysis.choices[0].message.content)
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  resp, _ := 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 legal document analyst. Analyze the provided document excerpts and answer the user's question. Always cite specific passages to support your analysis."),
  		},
  		{
  			Role:    casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleUser),
  			Content: casedev.F("## Document Excerpts\n\n" + chunks + "\n\n## Sources\n" + sources + "\n\n## Question\nWhat are the key terms of this agreement?"),
  		},
  	}),
  	Temperature: casedev.F(float64(0.3)),
  })
  fmt.Println(resp.Choices[0].Message.Content)
  ```
</CodeGroup>

```json title="Response" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
{
  "choices": [{
    "message": {
      "content": "Based on the contract excerpts, the key terms include:\n\n1. **Payment Terms**: Section 3.2 states that payment is due within 30 days...\n\n2. **Termination**: Either party may terminate with 90 days written notice (Section 7.1)...\n\n3. **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:

<CodeGroup>
  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  # 1. Search vault for relevant documents
  casedev vault search --id $VAULT_ID \
    --query "What are the indemnification clauses?" \
    --method hybrid --limit 10

  # 2. Analyze with LLM
  casedev llm:v1:chat create-completion \
    --model anthropic/claude-sonnet-4.5 \
    --message '{role: system, content: "You are a senior legal analyst. Answer using only the provided excerpts."}' \
    --message '{role: user, content: "<document excerpts from search>\n\nQuestion: What are the indemnification clauses?"}' \
    --temperature 0.3
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  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);
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import os
  import casedev

  client = casedev.Casedev(api_key=os.environ['CASEDEV_API_KEY'])

  def analyze_documents(vault_id: str, query: str) -> dict:
      # 1. Search
      search_results = client.vault.search(vault_id,
          query=query,
          method='hybrid',
          top_k=10
      )

      chunks = '\n\n'.join(c.text for c in search_results.chunks)
      sources = ', '.join(s.filename for s in search_results.sources)

      # 2. Analyze
      analysis = client.llm.v1.chat.create_completion(
          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': f'## 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': search_results.sources,
          'usage': analysis.usage
      }

  # Run it
  result = analyze_documents('vault_abc123', 'What are the indemnification clauses?')
  print(result['answer'])
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  // 1. Search vault
  searchResults, _ := client.Vault.Search(ctx, vaultID, casedev.VaultSearchParams{
  	Query:  casedev.F(query),
  	Method: casedev.F(casedev.VaultSearchParamsMethodHybrid),
  	TopK:   casedev.F(int64(10)),
  })

  // Build context from chunks
  var chunks string
  for _, c := range searchResults.Chunks {
  	chunks += c.Text + "\n\n"
  }
  var sources string
  for _, s := range searchResults.Sources {
  	sources += s.Filename + ", "
  }

  // 2. Analyze with 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 senior legal analyst. Answer questions using only the provided document excerpts. Cite sources."),
  		},
  		{
  			Role:    casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleUser),
  			Content: casedev.F("## Document Excerpts\n\n" + chunks + "\n\n## Question\n" + query),
  		},
  	}),
  	Temperature: casedev.F(float64(0.3)),
  })
  fmt.Println(analysis.Choices[0].Message.Content)
  ```
</CodeGroup>

***

## Extending the Analyzer

### Add Entity Extraction

Run a second LLM pass to extract structured entities:

<CodeGroup>
  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  casedev llm:v1:chat create-completion \
    --model openai/gpt-4o \
    --message '{role: system, content: "Extract named entities as JSON: {people: [], organizations: [], dates: [], locations: [], monetary_amounts: []}"}' \
    --message '{role: user, content: "<document text>"}' \
    --temperature 0
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  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
  });
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  entities = client.llm.v1.chat.create_completion(
      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
  )
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  entities, _ := client.Llm.V1.Chat.NewCompletion(ctx, casedev.LlmV1ChatNewCompletionParams{
  	Model: casedev.F("openai/gpt-4o"),
  	Messages: casedev.F([]casedev.LlmV1ChatNewCompletionParamsMessage{
  		{
  			Role:    casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleSystem),
  			Content: casedev.F("Extract named entities as JSON: {people: [], organizations: [], dates: [], locations: [], monetary_amounts: []}"),
  		},
  		{
  			Role:    casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleUser),
  			Content: casedev.F(chunks),
  		},
  	}),
  	Temperature: casedev.F(float64(0)),
  })
  fmt.Println(entities.Choices[0].Message.Content)
  ```
</CodeGroup>

### Generate a PDF Report

Convert the analysis into a formatted document:

<CodeGroup>
  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  casedev format:v1 document \
    --content "# Report" \
    --input-format md --output-format pdf
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  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'
  });
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  report = client.format.v1.create_document(
      content=f'# Legal Analysis Report\n\n**Query:** {query}\n\n{analysis.choices[0].message.content}',
      input_format='md',
      output_format='pdf'
  )
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  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
  ```
</CodeGroup>

***

## Production Tips

### Error Handling

<CodeGroup>
  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  # CLI displays errors to stderr with status codes
  casedev vault search --id $VAULT_ID --query "analysis query"
  # Error: 404 Not Found — check your vault ID
  # Error: 429 Too Many Requests — retry after a delay
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  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);
    }
  }
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  try:
      result = analyze_documents(vault_id, query)
      print(result['answer'])
  except casedev.NotFoundError:
      print('Vault not found — check your vault ID')
  except casedev.RateLimitError:
      print('Rate limited — retry after a delay')
  except casedev.APIError as e:
      print(f'Analysis failed: {e.message}')
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  result, err := analyzeDocuments(ctx, vaultID, query)
  if err != nil {
  	var apiErr *casedev.Error
  	if errors.As(err, &apiErr) {
  		switch apiErr.StatusCode {
  		case 404:
  			fmt.Println("Vault not found — check your vault ID")
  		case 429:
  			fmt.Println("Rate limited — retry after a delay")
  		default:
  			fmt.Printf("Analysis failed: %s\n", apiErr.Message)
  		}
  	}
  }
  ```
</CodeGroup>

<Tip>
  Use `temperature: 0` for factual extraction tasks. Try cheaper models like `deepseek/deepseek-chat` for simpler analysis.
</Tip>

***

## Next Steps

* [Vault Search Reference](/vault/search) — Advanced search methods and filtering
* [Chat Completions](/llms/chat-completions) — Streaming, system prompts, and more
* [Format Service](/format/index) — PDF and DOCX generation
