Extracta Manual, as shipped in the download
Extracta — User Manual
Invoice and document data extraction you run yourself. Version 3.1.4 [src: app/controllers/api.php:64].
About this manual
Every statement here was written by reading Extracta's own source, and each one carries the file it came from in a bracketed src marker. Paths are relative to the folder this docs directory sits in. If a sentence disagrees with the application, the application is right and this manual has a bug — the markers exist so you can check rather than take our word for it.
Nothing is described that the code does not do. Where something is deliberately limited, that is said plainly rather than left out.
What Extracta is
Upload a document, have a language model read the figures off it, then correct anything it got wrong and export the result. The route through the application is exactly that shape: upload [src: app/index.php:70], extract [src: app/index.php:71], review [src: app/index.php:72], export [src: app/index.php:75].
The part worth understanding first
A language model reads your invoice, and a language model will occasionally be wrong. Extracta is built around that fact rather than in spite of it, in three stages.
The model produces loose JSON. Whatever it returns comes back with the keys the model chose to use [src: app/src/Llm.php:182].
Deterministic code makes it canonical. That raw output is passed through a normaliser which pulls known fields out of the variants a model might emit, and converts every value to one fixed shape: money as integer cents, dates as ISO or null [src: app/src/Extraction.php:17].
Then the arithmetic is checked. A reconciliation step cross-checks the numbers against each other and reports what does not add up [src: app/src/Extraction.php:218].
The model is asked to read; it is not trusted to be consistent. Everything after the reading is ordinary code you can audit.
Which model reads your documents
You choose the provider, and you supply the credential. Three are supported [src: app/src/Llm.php:15]:
| Provider | Default model | Notes |
|---|---|---|
| OpenAI | gpt-4o-mini | Image sent as a data URL, JSON response format requested |
| Anthropic | claude-haiku-4-5-20251001 | Handles PDFs natively as well as images |
| Ollama | llama3.2-vision | Runs locally; no authentication required |
The provider is read from your settings and an unknown one is refused rather than silently defaulted [src: app/src/Llm.php:187].
The defaults live in one list [src: app/src/Llm.php:47]. Anthropic retired the earlier default, claude-3-haiku-20240307, on 20 April 2026; an install that still has a retired Anthropic model saved uses the replacement Anthropic names for it, and Settings says so [src: app/src/Llm.php:91]. A model the provider refuses (retired, misspelt, or not pulled on your Ollama server) stops the extraction with a message that names the model and what to change [src: app/src/Llm.php:108].
Two limits apply to every call: a ninety-second timeout [src: app/src/Llm.php:32] and an output ceiling of 8,192 tokens [src: app/src/Llm.php:38]. That ceiling was raised from 2,048, and the source notes why raising it costs nothing — providers bill for the tokens actually produced, not the ceiling you allow [src: app/src/Llm.php:34].
The Ollama option is the one to notice if the documents matter: it runs against a model on your own machine, so the invoice never leaves it.
The canonical fields
Every document is reduced to the same set: vendor, invoice number, invoice date, due date, currency, subtotal, tax and total [src: app/src/Extraction.php:17]. Line items are kept alongside them.
Money is held as integer cents and dates as ISO Y-m-d or nothing at all [src: app/src/Extraction.php:17]. There is no third state where a date is "probably the fourth of March".
Money
The money parser handles what real documents contain: currency symbols and stray letters, the American 1,234.56 and the European 1.234,56, spaces used as thousands separators, negatives written either with a minus or in parentheses, and raw JSON numbers where the model returned one [src: app/src/Extraction.php:27].
Where there is no number at all it returns nothing rather than zero [src: app/src/Extraction.php:32]. A missing total and a total of zero are different facts and stay different.
Dates, and the 04/03 problem
Dates arrive in whatever form the document used. The normaliser accepts ISO dates, textual months like "Apr 3, 2026" or "3 April 2026", and numeric dates separated by slashes, dots or hyphens [src: app/src/Extraction.php:91].
Numeric dates are where the care shows. A four-digit leading number is treated as a year. For the ambiguous rest, the first number is the day if it exceeds twelve, the second is the day if it exceeds twelve, and only when both are twelve or under does the decision fall back to your day-first setting [src: app/src/Extraction.php:96]. Whatever comes out is validated as a real calendar date before it is accepted, and anything that fails becomes nothing rather than a guess [src: app/src/Extraction.php:96].
The day-first preference is a setting, so a European install can be told once that 04/03 means the fourth of March [src: app/controllers/app.php:484].
Currency
A three-letter code is derived from either a code or a symbol, covering the common ones, and returns empty where it cannot tell [src: app/src/Extraction.php:126].
The arithmetic check
After normalisation the record is cross-checked, and this is the feature that earns its place [src: app/src/Extraction.php:184].
Two independent checks run: that the line items sum to the subtotal — or to the total, where there is no subtotal — and that subtotal plus tax equals the total [src: app/src/Extraction.php:218].
Three details make it useful rather than noisy. Each check is skipped when its inputs are absent, so a document with no line items does not generate a complaint about line items. A small tolerance is allowed for the rounding a model may introduce. And the warning names the figures, telling you what the lines sum to and what the subtotal says, rather than reporting that something is wrong [src: app/src/Extraction.php:218].
The result is a flag and a list of warnings, not a refusal. Extracta tells you where the arithmetic disagrees and leaves the judgement to you.
Reviewing
Extraction requires the review permission, as does the review screen itself [src: app/index.php:71]. Correcting a value and saving it is what moves a document out of the queue [src: app/index.php:73].
A possible duplicate is flagged on the review screen [src: app/controllers/app.php:240]: the same vendor (letters and digits, any case) with the same invoice number, or with the same total on the same invoice date, as a document already in Extracta [src: app/src/Extraction.php:198]. The warning links to the other document; nothing is merged or deleted, and the decision stays yours.
Exporting
Exports come out as CSV or JSON [src: app/index.php:75].
The CSV is written properly rather than by joining strings with commas: a cell is quoted when it contains a comma, a quote, a carriage return or a line feed, inner quotes are doubled, and lines end with CRLF as the format requires [src: app/src/Extraction.php:246]. Money is written as decimals in the export even though it is held as cents internally [src: app/src/Extraction.php:259].
There are also named presets whose column sets match documented import formats, so the file can go straight into the ledger that expects it [src: app/src/Extraction.php:264].
Users, roles and what each may do
Three roles [src: app/controllers/v3.php:44].
| Permission | viewer | member | admin |
|---|---|---|---|
document.view | yes | yes | yes |
template.view | yes | yes | yes |
audit.view | yes | yes | yes |
document.upload | — | yes | yes |
document.review | — | yes | yes |
view.save | — | yes | yes |
| everything else | — | — | yes |
The source describes the intent directly: a viewer is a finance seat that reads the register without touching the numbers, a member is the reviewer who corrects extracted values and clears the queue, and templates, the language-model credential and the register's erasure powers are admin only [src: app/controllers/v3.php:44].
Settings
One form holds the company name, the currency, the language-model provider, model, API key and base URL, the day-first date preference and the timezone [src: app/controllers/app.php:484]. A second holds the mail configuration [src: app/controllers/app.php:501].
One behaviour is worth knowing because it prevents a common accident: leaving the SMTP password blank keeps the stored one rather than clearing it, so saving the settings form without retyping the password does not break working mail [src: app/controllers/app.php:511].
The API and agent access
Machine access uses a key [src: app/index.php:83]. Documents can be listed and fetched, a review can be submitted, and the export can be pulled [src: app/index.php:84].
Five tools are exposed to an agent, and unlike some products in this range two of them write [src: app/controllers/v3.php:44]:
| Tool | Writes? | What it does |
|---|---|---|
list_documents | no | Documents with status and whether they have been reviewed |
document_detail | no | One document with its extracted fields and the arithmetic check |
export_rows | no | The extracted rows as structured data, ready for a ledger |
submit_document | yes | Records extracted values for a document |
review_decide | yes | Approves a document as reviewed, or sends it back |
The two writers are the review workflow, not the extraction: an agent can record values and clear the queue, and the values it submits go through the same normalisation as everything else [src: app/controllers/v3.php:44].
The public demo never calls a real model
Where the application is running as a demonstration, extraction does not reach any provider. A fixture is returned instead, flagged as simulated and carrying a note saying so, and the demo allows only a few extractions per session [src: app/controllers/app.php:163].
That matters for two reasons: a public demo cannot spend somebody's API budget, and nothing anyone uploads to a demo is sent to a third party. The demo's upload page says so before the upload, not only after it.
What Extracta does not do
It does not guarantee the figures are right. A language model reads the document, and the checks that follow test the record only against itself — whether the lines sum to the subtotal, and whether subtotal plus tax equals the total [src: app/src/Extraction.php:218]. Nothing compares any figure with the document it came from. A plausible wrong number that happens to be arithmetically consistent passes both checks, which is why extraction lands in a review queue rather than an export [src: app/index.php:72].
It does not decide anything about your accounts. It extracts, checks arithmetic and exports; what the numbers mean is yours.