Receipt Scanning

Photograph a receipt and have the transaction filled in for you. Off by default — a Nextcloud administrator has to turn it on and choose who reads the images.

Overview#

Receipt scanning takes a photo of a till receipt and turns it into a draft transaction: the merchant, the date, the total, and a suggested category. You check it and save it, or correct it first. Nothing is recorded without you seeing it.

Reading a photograph needs an OCR or vision model, and that model has to run somewhere. This page is mostly about choosing that somewhere, because it decides who else sees your receipts.

The short version: until an administrator configures a provider, this app sends receipt images nowhere at all. There is no default backend and no fallback.

How the images travel#

Whatever you choose, the shape is the same:

Your phone or browser  →  your Nextcloud  →  the OCR provider
                                          ←  merchant, date, total

Your Nextcloud server makes the request, not your phone and not your browser. Two things follow from that:

  • The credential lives on the server. No API key is ever stored in the phone app or sent to a browser. An admin who saves a key cannot read it back out — the settings page reports only that one exists.
  • A provider on your own network works. Because the request comes from the server, a machine that is not reachable from the internet is a perfectly good backend. This is the most private option, and it is deliberately a first-class one rather than a footnote.

Only the image goes to the provider. Your account names, balances, other transactions and category tree are not sent.

Scanning a receipt#

Once an administrator has set a provider up, a Scan a receipt box appears in the Add Transaction dialog, under Extras. Drop a photo onto it, or click it to pick one.

The image goes to your server, comes back read, and the form fills itself in: date, amount, merchant (as both the description and the vendor), and a category if one of your rules matches the shop. The box then tells you what it filled, how many items it found on the receipt, and whether those items add up to the printed total.

Nothing is saved until you press Save. Check the figures first — a bad read costs you a retype, not a wrong record. When you do save, the photo is attached to the transaction, so a receipt is one action instead of three.

You can scan while editing an existing transaction too. There it only fills fields you have left empty — a scan is a suggestion, and it will not overwrite something you typed. Clear a field and scan again if you want the receipt's version of it.

If the box is not there, this server has no OCR provider configured; ask your administrator.

Splitting a receipt by item#

If the items on the receipt add up, the box also offers Split into N lines. Take it and the transaction becomes one split per item, so a single shop trip can put groceries, household and a treat in three different categories.

Where the receipt prints tax separately, the tax becomes its own line at the printed amount — it is not spread across the items, so every figure still matches the receipt in your hand. Each line starts uncategorised; set the categories, then save.

The offer only appears when the arithmetic reconciles. Four shapes do, which covers most real receipts:

ShapeTypical receipt
items = totalTax-inclusive prices, no offers
items + tax = totalTax printed separately
items − savings = totalLoyalty card, coupons, multibuy
items − savings + tax = totalBoth

Loyalty savings are handled the same way as tax: their own line, at the printed amount, but negative — a supermarket basket adds up to more than you paid, and the savings line is what closes the gap. So a Clubcard receipt splits properly rather than being refused.

An item the reader could not make out, or a receipt that fits none of these shapes, stays a single transaction rather than producing splits that would not balance — and the box says so instead of leaving you guessing.

Splits carry a category each. Tags still apply to the transaction as a whole.

Where receipts are filed#

Receipts go into your own Files under Budget/Receipts/<year>/<month>/, and an uploaded photo is named after the transaction:

Budget/Receipts/2026/08/2026-08-05 The Corner Deli 23.77.jpg

That keeps the folder in date order and lets you find a receipt by shop or by amount with Nextcloud's normal search, instead of hunting through camera filenames. A receipt you attach from your own Files keeps its existing name — it is your file, so it is linked rather than renamed. Removing a receipt from a transaction never deletes the file, and receipts filed before this change stay where they are.

Tip: a flat, well-lit photo of the whole receipt reads best. Getting the total right matters most — if the reader cannot find a total it reports a failure rather than filling in half a transaction.

Choosing a provider (admin)#

Go to Settings → Admin Settings → Receipt scanning. Four options:

ProviderWhere the image goesNeeds
OffNowhere.
Nextcloud AIWhatever AI backend this Nextcloud is already set up to use.An AI app configured on the server
Custom endpointAny OpenAI-compatible server you name.A URL and a model name
Otherworld relayOtherworld's hosted service.A license key

Off#

The default. No provider is contacted, the capture flows stay hidden in the app, and the API reports the feature as unavailable so a phone client hides it too rather than failing at the point of use.

Nextcloud AI#

Uses Nextcloud's own AI provider framework, so receipt scanning inherits whatever backend you already chose for the rest of your instance — and whatever privacy policy came with it. If that is a local model, images never leave the server. If it is a cloud service, images go to that company.

If no AI provider is configured on the instance, the option still appears but says so, and receipt scanning stays off until one is set up.

Custom endpoint#

Any server speaking the OpenAI-compatible API. This covers a local Ollama running a vision model, a self-hosted vLLM, or a commercial API.

You need:

  • Endpoint URL — the base URL, e.g. http://192.168.1.10:11434/v1. Plain http:// is accepted for local addresses on purpose.
  • Model — the vision model to ask for, exactly as the endpoint names it, e.g. qwen2.5vl. It must be able to read images; a text-only model will not work.
  • API key — optional. A local Ollama usually needs none; a commercial API will.

A vision model on your own hardware is the only arrangement where receipt images never leave your control at all.

Private addresses need one server setting. Nextcloud's HTTP client refuses to call private and local addresses by default — a security measure against request forgery. If your endpoint lives on a LAN address (192.168.x.x, 10.x.x.x, a .local/.lan hostname), the administrator must add 'allow_local_remote_servers' => true, to config/config.php, or every scan will fail with a provider error even though the settings look correct. Endpoints on public hostnames are unaffected.

Otherworld relay#

A hosted service run by Otherworld, authenticated with a license key bought from budget.otherworld.dev. Images are read and discarded — not stored, and not used to train anything. Use this if you want scanning to work without running or paying for a model yourself.

The API key#

Keys are stored encrypted, using Nextcloud's own encryption, and are never returned to a browser or included in any API response.

Practically, that means the settings field is always blank when you open it:

  • Leave it blank to keep the key that is already saved.
  • Type a new key to replace it.
  • Remove key to delete it. Scanning stops until a new one is saved.

An admin who has lost a key re-enters it rather than reading it back. That is deliberate.

From the command line#

On a headless or scripted install, occ budget:ocr does the same job as the settings page:

# What is configured right now, and whether it can actually run
occ budget:ocr

# Point at a vision model on your own network
occ budget:ocr set --provider=custom \
                   --endpoint=http://192.168.1.10:11434/v1 \
                   --model=qwen2.5vl:7b

# Otherworld relay, reading the licence key from stdin so it stays
# out of your shell history
echo 'lic_xxx' | occ budget:ocr set --provider=relay --api-key-stdin

# Turn it off again; --api-key="" removes a stored key
occ budget:ocr set --provider=none

Use this rather than occ config:app:set budget ocr_…. Setting the raw config values looks equivalent but skips the provider and URL checks — and it would write your API key to the database in plaintext, because the encryption happens in the app, not in Nextcloud's config layer. budget:ocr takes the same path as the settings page: same validation, same encryption, and a rejected value changes nothing at all.

Privacy, plainly#

  • While the provider is Off, this app makes no external calls for receipts under any circumstances.
  • Only the receipt image is sent — never your ledger.
  • The image is sent by your server, so with a local provider nothing leaves your network.
  • With Nextcloud AI, the destination is whatever your instance is configured to use; check that app's own terms.
  • With a custom endpoint, the destination is whatever you typed in.
  • With the relay, images reach Otherworld, are processed, and are discarded.
  • Receipts you attach to a transaction are stored in your own Nextcloud Files under Budget/Receipts/, exactly as they were before this feature existed.

Limits#

Receipt scanning reads a receipt; it does not audit it. Expect to check the result:

  • Faded, crumpled or partly cut-off receipts read badly, as they would for a person.
  • Handwritten totals are unreliable.
  • A suggested category is a guess based on the merchant, matched against your own category tree. It is never applied without you seeing it.
  • Where the arithmetic on the receipt does not add up, the app trusts the printed total rather than the sum of the lines, and tells you it did.
  • Transactions — attaching receipts to a transaction by hand
  • REST API — how a phone app discovers whether this server has scanning enabled
  • Settings — the rest of the settings page