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