Skip to main content
The problem: Opposing counsel sent you 500 pages of blurry photocopies. You need to search them, but they’re just images. The solution: Run OCR to extract text, then search or analyze with AI.

1. Submit for OCR

curl -X POST https://api.case.dev/ocr/v1/process \
  -H "Authorization: Bearer $CASEDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "document_url": "https://your-storage.com/user-upload.pdf",
    "engine": "doctr",
    "features": {"embed": {}}
  }'
casedev ocr:v1 process \
  --document-url "https://storage.example.com/document.pdf"
import Casedev from 'casedev';

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

// Process a document uploaded by your user
const job = await client.ocr.v1.process({
  document_url: documentUrl, // URL from your user's upload
  engine: 'doctr',  // Fast, good for printed text
  features: {
    embed: {}  // Generate searchable PDF
  }
});

console.log(`OCR job started: ${job.id}`);
import casedev
import os

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

# Process a document uploaded by your user
job = client.ocr.v1.process(
    document_url=document_url,  # URL from your user's upload
    engine='doctr',  # Fast, good for printed text
    features={
        'embed': {}  # Generate searchable PDF
    }
)

print(f'OCR job started: {job.id}')
job, _ := client.Ocr.V1.Process(ctx, casedev.OcrV1ProcessParams{
	DocumentURL: casedev.F("https://storage.example.com/document.pdf"),
})
fmt.Println(job.ID)

2. Wait for completion

OCR runs asynchronously. Poll for status or use webhooks to notify your users:
# Poll for status
curl "https://api.case.dev/ocr/v1/$JOB_ID" \
  -H "Authorization: Bearer $CASEDEV_API_KEY"
casedev ocr:v1 retrieve --id $JOB_ID
// Poll for completion
let result = await client.ocr.v1.retrieve(job.id);

while (result.status === 'processing' || result.status === 'pending') {
  console.log(`Status: ${result.status} (${result.chunks_completed}/${result.chunk_count} pages)`);
  await new Promise(r => setTimeout(r, 5000));
  result = await client.ocr.v1.retrieve(job.id);
}

if (result.status === 'completed') {
  console.log(`✅ OCR complete! ${result.page_count} pages processed.`);
  console.log(`Confidence: ${(result.confidence * 100).toFixed(1)}%`);
}
import time

# Poll for completion
result = client.ocr.v1.retrieve(job.id)

while result.status in ['processing', 'pending']:
    print(f'Status: {result.status} ({result.chunks_completed}/{result.chunk_count} pages)')
    time.sleep(5)
    result = client.ocr.v1.retrieve(job.id)

if result.status == 'completed':
    print(f'✅ OCR complete! {result.page_count} pages processed.')
    print(f'Confidence: {result.confidence * 100:.1f}%')
result, _ := client.Ocr.V1.Get(ctx, jobID)
fmt.Println(result.Status)

3. Download results

Provide extracted text, structured data, or a searchable PDF:
# Download text
curl "https://api.case.dev/ocr/v1/$JOB_ID/download/text" \
  -H "Authorization: Bearer $CASEDEV_API_KEY" \
  -o extracted.txt

# Download searchable PDF
curl "https://api.case.dev/ocr/v1/$JOB_ID/download/pdf" \
  -H "Authorization: Bearer $CASEDEV_API_KEY" \
  -o searchable.pdf
casedev ocr:v1 download --id $JOB_ID --type text
// Download plain text for your user
const text = await client.ocr.v1.download(job.id, 'text');

// Download searchable PDF (original with invisible text layer)
const pdf = await client.ocr.v1.download(job.id, 'pdf');
fs.writeFileSync('searchable-document.pdf', Buffer.from(pdf));

// Download structured JSON (with word coordinates for highlighting)
const json = await client.ocr.v1.download(job.id, 'json');
console.log(`Extracted ${json.pages.length} pages`);
# Download plain text for your user
text = client.ocr.v1.download(job.id, 'text')

# Download searchable PDF (original with invisible text layer)
pdf = client.ocr.v1.download(job.id, 'pdf')
with open('searchable-document.pdf', 'wb') as f:
    f.write(pdf)

# Download structured JSON (with word coordinates for highlighting)
json_data = client.ocr.v1.download(job.id, 'json')
print(f'Extracted {len(json_data["pages"])} pages')
text, _ := client.Ocr.V1.Download(ctx, jobID, casedev.OcrV1DownloadParamsTypeText)
// text is *http.Response with content body

4. Analyze with AI

Enhance your feature with automatic data extraction:
curl -X POST https://api.case.dev/llm/v1/chat/completions \
  -H "Authorization: Bearer $CASEDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-4.5",
    "messages": [
      {"role": "system", "content": "Extract key dates, parties, and claims. Format as JSON."},
      {"role": "user", "content": "[OCR TEXT]"}
    ],
    "temperature": 0
  }'
casedev llm:v1:chat create-completion \
  --model anthropic/claude-sonnet-4.5 \
  --message '{role: system, content: "Extract key dates, parties, and claims from this document. Format as JSON."}' \
  --message '{role: user, content: "<OCR text>"}' \
  --temperature 0
// Extract key information for your user
const analysis = await client.llm.v1.chat.createCompletion({
  model: 'anthropic/claude-sonnet-4.5',
  messages: [
    {
      role: 'system',
      content: 'Extract key dates, parties, and claims from this document. Format as JSON.'
    },
    {
      role: 'user',
      content: text
    }
  ],
  temperature: 0  // Deterministic for factual extraction
});

// Return structured data to your user
console.log(analysis.choices[0].message.content);
# Extract key information for your user
analysis = client.llm.v1.chat.create_completion(
    model='anthropic/claude-sonnet-4.5',
    messages=[
        {
            'role': 'system',
            'content': 'Extract key dates, parties, and claims from this document. Format as JSON.'
        },
        {
            'role': 'user',
            'content': text
        }
    ],
    temperature=0  # Deterministic for factual extraction
)

# Return structured data to your user
print(analysis.choices[0].message.content)
// Extract key information for your user
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("Extract key dates, parties, and claims from this document. Format as JSON."),
		},
		{
			Role:    casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleUser),
			Content: casedev.F(text), // OCR-extracted text
		},
	}),
	Temperature: casedev.F(float64(0)),
})
fmt.Println(resp.Choices[0].Message.Content)

OCR engines

Choose the right engine based on your users’ document types:
EngineBest forSpeed
doctrClean printed textFast
paddleocrTables, forms, complex layoutsSlower
Recommendation: Start with doctr for most use cases. Switch to paddleocr if your users need table extraction or have complex document layouts.