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

# Quickstart

> Extract structured data from a document in under 10 minutes

Build your first document extraction flow using the Doclo SDK.

## Prerequisites

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

## Installation

Install the core packages:

```bash theme={null}
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.

<Steps>
  <Step title="Get an OpenRouter API Key">
    Sign up at [openrouter.ai](https://openrouter.ai), navigate to the Keys section, and generate a new API key.
  </Step>

  <Step title="Create environment file">
    Create a `.env.local` file in your project root:

    ```bash theme={null}
    OPENROUTER_API_KEY=sk-or-v1-your-key-here
    ```
  </Step>

  <Step title="Load environment variables">
    For Node.js scripts, install dotenv:

    ```bash theme={null}
    pnpm add dotenv
    ```

    Then import it at the top of your script:

    ```typescript theme={null}
    import 'dotenv/config';
    ```
  </Step>
</Steps>

## Your First Flow: Invoice Extraction

Create a file called `invoice-extract.ts`:

```typescript theme={null}
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

<Steps>
  <Step title="Add a test PDF">
    Save an invoice PDF as `invoice.pdf` in your project directory.
  </Step>

  <Step title="Run the script">
    ```bash theme={null}
    npx tsx invoice-extract.ts
    ```
  </Step>

  <Step title="View the output">
    ```typescript theme={null}
    {
      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", ... }
      }
    }
    ```
  </Step>
</Steps>

## Understanding the Flow

This example demonstrates two key Doclo features:

| Feature                   | What it does                                                     |
| ------------------------- | ---------------------------------------------------------------- |
| **Quality-based routing** | Assesses document quality, routes to the right model for the job |
| **Consensus voting**      | Runs 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:

| Property     | Description                                |
| ------------ | ------------------------------------------ |
| `output`     | Final extracted data matching your schema  |
| `aggregated` | Totals: duration, cost, tokens, step count |
| `metrics`    | Per-step timing and cost breakdown         |
| `artifacts`  | Intermediate outputs from each step        |

## Alternative Providers

Use `createVLMProvider` for a single provider, or `buildLLMProvider` for fallback chains:

<Tabs>
  <Tab title="Single Provider">
    ```typescript theme={null}
    import { createVLMProvider } from '@doclo/providers-llm';

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

  <Tab title="With Fallback">
    ```typescript theme={null}
    import { buildLLMProvider } from '@doclo/providers-llm';

    const provider = buildLLMProvider({
      providers: [
        { provider: 'anthropic', model: 'anthropic/claude-sonnet-4', apiKey: process.env.OPENROUTER_API_KEY!, via: 'openrouter' },
        { provider: 'openai', model: 'openai/gpt-5.1', apiKey: process.env.OPENROUTER_API_KEY!, via: 'openrouter' }
      ],
      maxRetries: 2
    });
    ```
  </Tab>

  <Tab title="Native Keys">
    ```typescript theme={null}
    import { createVLMProvider } from '@doclo/providers-llm';

    // Use provider directly without OpenRouter
    const provider = createVLMProvider({
      provider: 'openai',
      model: 'gpt-4o',
      apiKey: process.env.OPENAI_API_KEY!
    });
    ```
  </Tab>
</Tabs>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Cannot find module '@doclo/flows'">
    Make sure packages are installed:

    ```bash theme={null}
    pnpm add @doclo/flows @doclo/providers-llm
    ```
  </Accordion>

  <Accordion title="OPENROUTER_API_KEY is undefined">
    * 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
  </Accordion>

  <Accordion title="429 Rate Limit Exceeded">
    * Check your [OpenRouter usage](https://openrouter.ai/activity)
    * Add credits to your account
    * Use `buildLLMProvider()` with retry logic for production
  </Accordion>

  <Accordion title="Schema validation failed">
    * 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
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Concepts" icon="book" href="/concepts/overview">
    Understand flows, nodes, and providers
  </Card>

  <Card title="Nodes Reference" icon="cube" href="/sdk/nodes">
    Explore all processing nodes
  </Card>

  <Card title="Providers" icon="plug" href="/sdk/providers">
    Configure LLM and OCR providers
  </Card>

  <Card title="Consensus Voting" icon="check-double" href="/sdk/advanced/consensus">
    Improve accuracy with multi-provider voting
  </Card>
</CardGroup>
