Skip to main content
Build your first document extraction flow using the Doclo SDK.

Prerequisites

Node.js 18+ installed
pnpm, npm, or yarn
Basic TypeScript knowledge
An AI provider API key (OpenRouter, OpenAI, or Anthropic)

Installation

Install the core packages:
pnpm add @doclo/flows @doclo/providers-llm
These two packages include everything you need:
  • @doclo/flows - Flow builder and all processing nodes
  • @doclo/providers-llm - LLM/VLM provider integrations

API Key Setup

This guide uses OpenRouter as a gateway to multiple AI providers. You can also use native provider keys directly.
1

Get an OpenRouter API Key

Sign up at openrouter.ai, navigate to the Keys section, and generate a new API key.
2

Create environment file

Create a .env.local file in your project root:
OPENROUTER_API_KEY=sk-or-v1-your-key-here
3

Load environment variables

For Node.js scripts, install dotenv:
pnpm add dotenv
Then import it at the top of your script:
import 'dotenv/config';

Your First Flow: Invoice Extraction

Create a file called invoice-extract.ts:
import 'dotenv/config';
import { createFlow, extract, categorize } from '@doclo/flows';
import { createVLMProvider } from '@doclo/providers-llm';
import fs from 'fs';

// Helper to convert file to base64 data URL
function fileToBase64(filePath: string): string {
  const fileBuffer = fs.readFileSync(filePath);
  const base64 = fileBuffer.toString('base64');
  const mimeType = filePath.endsWith('.pdf') ? 'application/pdf' : 'image/jpeg';
  return `data:${mimeType};base64,${base64}`;
}

// Providers for different document qualities
const proProvider = createVLMProvider({
  provider: 'google', model: 'google/gemini-2.5-pro',
  apiKey: process.env.OPENROUTER_API_KEY!, via: 'openrouter'
});
const flashProvider = createVLMProvider({
  provider: 'google', model: 'google/gemini-2.5-flash',
  apiKey: process.env.OPENROUTER_API_KEY!, via: 'openrouter'
});
const liteProvider = createVLMProvider({
  provider: 'google', model: 'google/gemini-2.5-flash-lite',
  apiKey: process.env.OPENROUTER_API_KEY!, via: 'openrouter'
});

// Schema for invoice extraction
const invoiceSchema = {
  type: 'object',
  properties: {
    invoiceNumber: { type: 'string' },
    vendor: { type: 'string' },
    date: { type: 'string' },
    total: { type: 'number' },
    currency: { type: 'string' }
  }
};

// Build the flow with quality-based routing
const flow = createFlow()
  // Assess document quality first
  .step('assess', categorize({
    provider: liteProvider,
    categories: ['low', 'medium', 'high'],
    additionalInstructions: 'Assess document quality: low = poor scan/handwritten, medium = decent quality, high = clean digital document'
  }))
  // Route to appropriate model based on quality
  .conditional('extract', (data) => {
    const options = { schema: invoiceSchema, consensus: { runs: 3, level: 'field', strategy: 'majority' } };
    switch (data.category) {
      case 'low':
        return extract({ provider: proProvider, ...options });
      case 'medium':
        return extract({ provider: flashProvider, ...options });
      default:
        return extract({ provider: liteProvider, ...options });
    }
  })
  .build();

// Run the flow
async function processInvoice(pdfPath: string) {
  const result = await flow.run({ base64: fileToBase64(pdfPath) });
  console.log(result);
  return result;
}

processInvoice('./invoice.pdf').catch(console.error);

Run the Example

1

Add a test PDF

Save an invoice PDF as invoice.pdf in your project directory.
2

Run the script

npx tsx invoice-extract.ts
3

View the output

{
  output: {
    invoiceNumber: "INV-2024-001",
    vendor: "Acme Corporation",
    date: "2024-01-15",
    total: 1250.00,
    currency: "USD"
  },
  aggregated: {
    totalDurationMs: 2134,
    totalCostUSD: 0.0045,
    totalInputTokens: 2400,
    totalOutputTokens: 320,
    stepCount: 2
  },
  metrics: [
    { step: "assess", ms: 312, costUSD: 0.0004 },
    { step: "extract", ms: 1822, costUSD: 0.0041 }
  ],
  artifacts: {
    assess: { category: "high" },
    extract: { invoiceNumber: "INV-2024-001", ... }
  }
}

Understanding the Flow

This example demonstrates two key Doclo features:
FeatureWhat it does
Quality-based routingAssesses document quality, routes to the right model for the job
Consensus votingRuns extraction 3 times and votes on each field for accuracy
The routing logic:
  • Low quality (poor scans, handwritten) → gemini-2.5-pro for maximum accuracy
  • Medium quality (decent scans) → gemini-2.5-flash for balanced performance
  • High quality (clean digital docs) → gemini-2.5-flash-lite for speed and cost
The result object contains:
PropertyDescription
outputFinal extracted data matching your schema
aggregatedTotals: duration, cost, tokens, step count
metricsPer-step timing and cost breakdown
artifactsIntermediate outputs from each step

Alternative Providers

Use createVLMProvider for a single provider, or buildLLMProvider for fallback chains:
import { createVLMProvider } from '@doclo/providers-llm';

const provider = createVLMProvider({
  provider: 'anthropic',
  model: 'anthropic/claude-sonnet-4',
  apiKey: process.env.OPENROUTER_API_KEY!,
  via: 'openrouter'
});

Troubleshooting

Make sure packages are installed:
pnpm add @doclo/flows @doclo/providers-llm
  • Check .env.local exists with your key
  • Make sure you imported dotenv/config at the top of your file
  • Restart your dev server if using Next.js
  • Check your OpenRouter usage
  • Add credits to your account
  • Use buildLLMProvider() with retry logic for production
  • Make required fields optional if data might not exist
  • Check your schema uses valid JSON Schema format
  • Review the error message for which field failed

Next Steps

Concepts

Understand flows, nodes, and providers

Nodes Reference

Explore all processing nodes

Providers

Configure LLM and OCR providers

Consensus Voting

Improve accuracy with multi-provider voting