Corvic AICorvic AI

Document Lens

Turn messy documents into clean data.

Most of your data is stuck in PDFs, scans and forms. A lens is agentic document processing: teach it once what to pull out, and every document — today’s and next year’s — comes back as structured data your stack can query.

Learns from a few. Runs on all of them.

Show a lens two or three examples and it works out the fields for itself. Approve them once and you can call it as often as you like. Pick a domain below — every one of these pages has something on it that the text layer alone gets wrong.

Invoices, statements and remittances, read against the contract that priced them.

The same typed fields off every document, each traced to its region.

NORTHWIND SUPPLYRotterdam DC · NetherlandsINVOICENo. 4471BILL TOAcme Robotics4 Bramley Way, Leeds LS11ISSUE DATE2026-07-14PO NUMBERPO-88190DESCRIPTIONQTYUNITAMOUNTSKU-2210Servo housing, anodised1582.901,243.50see Am. 3 — 79.69SKU-1180Bearing kit634.30205.80FRT-01Freight, Rotterdam DC192.0092.00Subtotal1541.30Sales tax117.00Amount dueUSD 1658.30Payment due 30 days from issue · Contract A-2231
Agreed fieldsextraction v4

Hover a row to see the region of the page it was read from.

Checks
  • subtotal + tax = amount_due
  • unit_price = contract_price
Reasoning

The arithmetic on the page is correct, but SKU-2210 is billed at list price. Amendment 3 to contract A-2231 prices it at 79.69, annotated by hand beside the line. At the contract price the amount owed is 1,610.15 — an overcharge of 48.15.

Getting the hard ones right

Anything can read a clean English invoice. The pile you actually have is scanned, annotated, badly photographed, inconsistent and not all in one language — and one wrong number is worse than no number.

  • It looks at the page

    Handwriting, stamps and layout are content, not just the text layer.

  • It checks its own work

    Say the line items must add up. Every reading is tested first.

  • It says when it is unsure

    When the page disagrees with itself you get told, not a quiet guess.

  • It reads any language

    Dates and numerals are normalised, whatever script they arrive in.

  • The same answer next month

    The wording is fixed, so next quarter reads like this quarter.

  • Versioned, not silently changed

    Edit the schema and the version bumps. Old readings still hold.

Agree once, save tokens forever

An agent asked to read a document works out what to look for every single time. A lens already knows — so each document is one bounded model call instead of an open-ended conversation.

  • Read one nowA document at a time

    An agent reads the pages properly. It catches the contradiction, reconciles the handwriting against the printed line, and tells you what it did.

    • For the hard ones, and for anything a person is waiting on
    • Reports repairs, contradictions and missing values
    • Called from your app, your agents, or MCP
  • Read the pileTen thousand documents

    The same agreed fields, run over the whole corpus on a schedule. It does not reason — that is exactly why it is cheap enough to run on everything.

    • One bounded model call per document, not a conversation
    • Only reads what is new on each run
    • Checks run in SQL over the extracted columns

You pick which serving you want per call, not per lens. Read the hard one properly while somebody waits, and read the other ten thousand overnight for a fraction of the tokens — off the same agreement, at the same version, producing the same fields.

Call it from your stack

You agree the fields in the product, in about the time it takes to check them by hand once. After that it is an API your code owns.

  1. 1

    Show it a few documents

    Two or three of the messy ones. It reads them and proposes the fields it found.

  2. 2

    Approve the fields

    Fix what it got wrong, add the checks that must hold, then agree it once.

  3. 3

    Point the pile at it

    It reads the rest, and everything that arrives after them, without being asked again.

read one document
# 1 · put a document inhttpx.post(f"{SOURCE_URL}/{doc}", content=pdf, headers=auth) # 2 · run the lens — the fields and checks are already agreedstarted = mcp.call_tool("start_extraction", {"lens_id": LENS, "document": doc}) # 3 · read what it wrotereading = mcp.call_tool("get_extraction", {"reading_id": started["reading_id"]})reading["fields"]["total"]   # 1658.30, with the region it came from
  • One bearer token authorises the upload, the run and the read.
  • Readings land in one table at an address that does not change.
  • Fields come back as JSON, so a schema change bumps a version instead of breaking your reader.
  • Failures are rows too — you can query which of last night's documents did not come back.

Point a lens at your hardest document

Upload two or three, approve what it finds, and let it read the rest of the pile. Free to start, no credit card.