# Invoice & Utility Bill Data Extractor: Totals, kWh, Line Items (`fayoussef/invoice-utility-bill-extractor`) Actor

Bulk extract vendor, dates, totals, tax, kWh and line items from invoice and utility bill PDFs and photos. Auto-detects each document, runs one or several AI models, checks the arithmetic and flags what to review.

- **URL**: https://apify.com/fayoussef/invoice-utility-bill-extractor.md
- **Developed by:** [Youssef Farhan](https://apify.com/fayoussef) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 extracted documents

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Invoice & Utility Bill Data Extractor: Totals, kWh, Line Items

Turn a folder of invoices and utility bills (PDF, scans or phone photos) into one clean spreadsheet row per document. It reads the vendor or provider, invoice or account number, dates, subtotal, tax, total, amount due, currency, kWh or other usage and every line item, then **checks the arithmetic itself** and tells you which documents a person should look at.

```
 PDF / scan / phone photo                     one row per document
 ┌──────────────────────┐                     ┌─────────────────────────────────────────────┐
 │ ACME Ltd   INV-1042  │   AI vision model   │ vendor   invoice_no  date        total  conf. │
 │ Hosting      120.00  │  ───────────────▶   │ ACME Ltd INV-1042    2026-03-02  144.00 high  │
 │ Tax 20%       24.00  │   + free math checks│ + line_items, tax, currency, review flag      │
 │ Total        144.00  │                     └─────────────────────────────────────────────┘
 └──────────────────────┘   subtotal + tax = total?   lines = subtotal?   dates in order?
```

Built for accounts payable teams, bookkeepers, energy audits, ESG and carbon reporting, and property managers: anyone who types numbers from invoices and bills into a sheet by hand.

### What makes it different

- **Invoices and utility bills in one run.** Auto-detect reads each file as an invoice or a utility bill and fills the matching columns, so a mixed folder just works. Or pick one type.
- **You choose the AI models, one or several.** Pick any vision model on OpenRouter. With two or more, every document is read by each model and the answers are compared field by field.
- **Confidence you can act on.** Free arithmetic checks run on every document (line items against subtotal and total, subtotal plus tax against total, charge lines against the bill total, meter readings against usage, date order, currency code). Optional: a cheap referee model re-reads the page, but only for fields where models disagree or a check failed.
- **Bulk without babysitting.** Hundreds of files from links or one ZIP upload. A bad link, a password-protected PDF or a file that is not a document becomes a line in a failure list, never a stopped run. Interrupted runs resume where they stopped.
- **Any other document too.** Switch to "custom fields only" and define your own fields for insurance letters, delivery notes, contracts or forms.
- **Full text is optional and off by default**, because AI models bill per output token.

### What you get per document

Columns for the other document type are left out of the row in auto-detect mode.

| Column | Meaning |
|---|---|
| `document_type` | invoice, utility_bill or other (auto-detect mode) |
| `invoice_number`, `vendor_name`, `vendor_tax_id`, `customer_name` | Invoice header |
| `invoice_date`, `due_date`, `po_number`, `payment_terms`, `iban` | Invoice dates and payment details |
| `subtotal`, `tax_total` | Before-tax total and total tax |
| `line_items` | Each invoice line with description, quantity, unit price and amount |
| `provider`, `service_type`, `account_number`, `service_address` | Utility bill header (electricity, gas, water, sewer, combined) |
| `statement_date`, `billing_period_start`, `billing_period_end` | Utility bill dates |
| `usage_kwh` | Electricity kWh for the bill, empty for gas and water |
| `usage_value`, `usage_unit`, `previous_reading`, `current_reading`, `tariff` | Main consumption with its printed unit, meter readings, rate plan |
| `charges`, `previous_balance` | Each utility charge line (credits negative) and the carried balance |
| `total_amount`, `amount_due`, `currency` | Document total, what is still payable now (0 if paid), ISO currency code. Kept apart on purpose |
| `confidence`, `needs_review`, `review_reasons` | See below |
| `models_used`, `models_agree`, `field_confidence`, `by_model` | Multi-model runs: who answered and where they differ |
| `full_text` | Whole transcription, only when you turn it on |
| `cost_usd`, `tokens` | Model usage for that document (billed separately, see Pricing) |

Dates are ISO (YYYY-MM-DD). Fields the document does not show are empty, never guessed. A water bill returns no kWh.

### How confidence works

| Label | Meaning |
|---|---|
| `high` | Two or more models agree on every field and every check passes |
| `medium` | One model with every check passing, or a referee settled a disagreement or a failed check |
| `low` | An unresolved disagreement, a failed check, or a key field is missing (invoices: number, vendor, date, total, currency; utility bills: provider, account, period, total, currency). `needs_review` is true and `review_reasons` says why |

Honest limits: agreement between models is evidence, not proof, and a single model can still be wrong in a way no check catches. Handwritten or very damaged documents often come back `low`, and that is the intended signal to look at them.

#### The referee option

Turn on "Check confidence with a cheap referee model" and, for each document, the referee looks at the page again but only for the disputed fields, and answers with the value it reads. This costs a fraction of a full read. The referee can only pick a value one of your models gave, unless an arithmetic check failed on that field, so it cannot invent new data.

### Input

| Field | What it does |
|---|---|
| Documents (links) | PDF, PNG, JPG, WEBP, TIFF or GIF links. Upload a text file with one link per line for big batches. Google Drive file links work when shared with anyone with the link |
| Documents (ZIP upload) | One ZIP with all files, any folder depth |
| What are you extracting from? | Auto-detect, invoices, utility bills, or any other document with your own fields |
| Extra fields | One per line, `name: what to look for`. Each becomes a column |
| Extra instructions | Free text, for example "amounts are in CAD" |
| AI models | One or more. The first is primary. Prices per million tokens are shown in the list |
| Referee model | Optional second look at disputed fields |
| Full text | Off by default |
| Extract every line item | On by default, used for the totals check |
| Cheaper, faster reading | Turns the models' hidden thinking off. Cheaper and faster, a little less accurate on dense documents |
| Maximum pages per document | Default 5. Raise it for long statements |
| Documents in parallel | Default 4 |

The form is pre-filled with three public samples (a Toronto Hydro bill and two invoices), so you can press Start and see a result.

Example:

```json
{
  "documentUrls": [{ "url": "https://example.com/invoices/2026-05.pdf" }],
  "documentType": "auto",
  "models": ["google/gemini-2.5-flash", "anthropic/claude-haiku-5.5"],
  "useRefereeModel": true
}
```

### Choosing models

Measured in October 2026 on documents I could check against the right answers: three sample invoices from the open-source invoice2data test set and two utility bills.

| Model | Result | Typical model cost per document |
|---|---|---|
| Gemini 2.5 Flash (default) | Invoice number, date, vendor, totals, tax and currency right on all three invoices. One of two utility bills had a wrong total choice and an adjusted kWh, and was correctly flagged for review | about $0.002 |
| Gemini 3 Flash | Every checked utility-bill field right on all three test bills | about $0.002 to $0.003 |
| Claude Haiku 5.5 | Every checked utility-bill field right on all three test bills | about $0.001 |
| Gemini 2.5 Flash Lite | Cheapest and fastest, misses some dense lines | about $0.0002 |
| Mistral Medium 3.1 | Good values, but its line sums often fail the total check | about $0.001 |
| DeepSeek V4.1 Flash | Very cheap but unreliable as a main reader, so it is the default referee only | under $0.001 |

A public benchmark (ArbitrAI OCR mini-bench, July 2026) also places Gemini models at or near the top on cost for quality across receipts, invoices and logistics documents. Treat it as a hint, not proof.

These are model costs measured on a handful of documents, not a promise, and prices change. The model list in the input form is refreshed from OpenRouter and always shows current prices. Dense multi-page statements cost more than a one-page invoice.

For an invoice-heavy workload, running two models together with the referee gives the best accuracy and flags the rest.

### Pricing

You pay per extracted document. Documents that could not be read are not charged and are listed in the `FAILED_DOCUMENTS` record. The current rate is shown on this Actor's Store page.

Model usage is a separate cost, and this Actor does not add a token meter on top of it:

- **Default:** AI usage runs through Apify's built-in OpenRouter access and is billed to your Apify account. No key needed.
- **Your own key:** add an OpenRouter key under "Bring your own API access" and usage is billed by OpenRouter on your account.

### Free plan

Free runs read the first 3 documents with one model. Apify's built-in AI access is not available on the free plan, so free runs need your own OpenRouter key. A paid [Apify plan](https://apify.com/pricing?fpr=youssef) removes both limits.

### Good to know

- PDFs are rendered to page images and, when they have a real text layer, that text is sent too, so digital invoices are read exactly.
- Day/month order on ambiguous dates is resolved from the document itself. If it cannot be resolved the date is left empty and the document is flagged.
- Files over 30 MB, password-protected PDFs, HEIC photos and nested ZIPs are reported as failures. Convert HEIC to JPG first.
- Documents in any language work. Values are copied in the language printed on the document.
- Who is the vendor and who is the customer is decided from the layout, so a document that shows only one party can read ambiguously. Check `needs_review` rows first.

### Support and custom work

Found a bug or a document that reads wrong? Open an Issue on this Actor and attach the file if you can.

💼 Need a custom version or another document type? [Contact me through my Apify profile](https://apify.com/fayoussef)

# Actor input Schema

## `documentUrls` (type: `array`):

Links to invoices and utility bills as PDF, PNG, JPG, WEBP, TIFF or GIF files. Pre-filled with three public samples (a Toronto Hydro bill and two invoices) so you can press Start and see the result. Replace them with your own links, or upload a text file with one link per line for big batches. Google Drive file links work if the file is shared with 'anyone with the link'.

## `documentsZip` (type: `string`):

Optional. Upload one ZIP file with all your invoices and bills inside (PDFs and images, any folder depth). Use this when you have many files on your computer.

## `documentType` (type: `string`):

Auto-detect reads each file as an invoice or a utility bill and fills the matching columns, so you can mix both in one run. Pick one type if all your files are the same. Custom fields only works on any other document: you decide every field below.

## `customFields` (type: `array`):

Optional for invoices and bills, required for other documents. One field per line, as 'name: what to look for', for example 'cost_center: the department code' or just 'policy number'. Each becomes a column.

## `instructions` (type: `string`):

Optional guidance for the model, for example 'amounts are in CAD', 'the seller is always Acme Ltd' or 'usage is in GJ'.

## `models` (type: `array`):

Pick one or more vision models. With two or more, every document is read by each model and the results are compared field by field. The first one is the primary. Prices shown are per million tokens, billed separately for model usage.

## `customModelIds` (type: `array`):

Optional. Any other OpenRouter vision model id that is not in the list above, for example 'vendor/model-name'. Added to the models you picked.

## `useRefereeModel` (type: `boolean`):

When on, a cheap model looks at the document again, but only for fields where your models disagree or where an arithmetic check failed (for example charge lines that do not add up to the total). It reads the page itself and settles those fields, so the cost is small. Documents it cannot settle are flagged for review.

## `refereeModel` (type: `string`):

Model used when the referee check is on. It only sees the disputed fields, so even a mid-priced model costs very little. A referee can only pick a value one of your models gave, unless an arithmetic check failed on that field.

## `includeFullText` (type: `boolean`):

Off by default. Adds a full_text column with the whole transcription. Models bill per output token, so turning this on makes each document noticeably more expensive to read.

## `cheaperFasterReading` (type: `boolean`):

Off by default. Switches off the hidden thinking step of models that have one. Reading costs about a third as much and is faster, but in tests it made a few more mistakes on dense or handwritten bills. Good for clean, standard bills.

## `extractLineItems` (type: `boolean`):

Returns each invoice line (quantity, unit price, amount) or each utility charge as a list, and uses it to check the totals. Turn off for a little less model cost.

## `maxPagesPerDocument` (type: `integer`):

Longer files are read up to this many pages. Raising it costs more model usage.

## `concurrency` (type: `integer`):

How many documents are processed at the same time. Lower it if your model account hits rate limits.

## `openrouterApiKey` (type: `string`):

Optional. Bring your own OpenRouter key and calls go straight to OpenRouter, billed by them on your own OpenRouter account instead of through Apify. Leave empty to use the built-in access, which needs no key at all.

## `apifyToken` (type: `string`):

Optional. Only needed when running outside the Apify platform. Leave empty on Apify.

## Actor input object example

```json
{
  "documentUrls": [
    {
      "url": "https://www.torontohydro.com/documents/20143/12589673/residential-tou-mobile.pdf/af45dbbf-8bb7-fb58-eee2-246ef8023b34?t=1779821331217"
    },
    {
      "url": "https://raw.githubusercontent.com/invoice-x/invoice2data/master/tests/compare/AzureInterior.pdf"
    },
    {
      "url": "https://raw.githubusercontent.com/invoice-x/invoice2data/master/tests/compare/QualityHosting.pdf"
    }
  ],
  "documentType": "auto",
  "customFields": [],
  "models": [
    "google/gemini-2.5-flash"
  ],
  "useRefereeModel": false,
  "refereeModel": "deepseek/deepseek-v4.1-flash",
  "includeFullText": false,
  "cheaperFasterReading": false,
  "extractLineItems": true,
  "maxPagesPerDocument": 5,
  "concurrency": 4
}
```

# Actor output Schema

## `results` (type: `string`):

One row per document. Opens the Overview view by default.

## `failed` (type: `string`):

Files that failed to download or open, with the reason. Empty when everything worked.

## `summary` (type: `string`):

Totals: documents read, flagged for review, failed, and the model cost of the run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "documentUrls": [
        {
            "url": "https://www.torontohydro.com/documents/20143/12589673/residential-tou-mobile.pdf/af45dbbf-8bb7-fb58-eee2-246ef8023b34?t=1779821331217"
        },
        {
            "url": "https://raw.githubusercontent.com/invoice-x/invoice2data/master/tests/compare/AzureInterior.pdf"
        },
        {
            "url": "https://raw.githubusercontent.com/invoice-x/invoice2data/master/tests/compare/QualityHosting.pdf"
        }
    ],
    "customFields": [],
    "models": [
        "google/gemini-2.5-flash"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fayoussef/invoice-utility-bill-extractor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "documentUrls": [
        { "url": "https://www.torontohydro.com/documents/20143/12589673/residential-tou-mobile.pdf/af45dbbf-8bb7-fb58-eee2-246ef8023b34?t=1779821331217" },
        { "url": "https://raw.githubusercontent.com/invoice-x/invoice2data/master/tests/compare/AzureInterior.pdf" },
        { "url": "https://raw.githubusercontent.com/invoice-x/invoice2data/master/tests/compare/QualityHosting.pdf" },
    ],
    "customFields": [],
    "models": ["google/gemini-2.5-flash"],
}

# Run the Actor and wait for it to finish
run = client.actor("fayoussef/invoice-utility-bill-extractor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "documentUrls": [
    {
      "url": "https://www.torontohydro.com/documents/20143/12589673/residential-tou-mobile.pdf/af45dbbf-8bb7-fb58-eee2-246ef8023b34?t=1779821331217"
    },
    {
      "url": "https://raw.githubusercontent.com/invoice-x/invoice2data/master/tests/compare/AzureInterior.pdf"
    },
    {
      "url": "https://raw.githubusercontent.com/invoice-x/invoice2data/master/tests/compare/QualityHosting.pdf"
    }
  ],
  "customFields": [],
  "models": [
    "google/gemini-2.5-flash"
  ]
}' |
apify call fayoussef/invoice-utility-bill-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fayoussef/invoice-utility-bill-extractor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/DZELowk8sgPdNVrd5/builds/AT6AZUymMPlM5kIqE/openapi.json
