Checking Receipts Against Expense Claims Automatically
Most expense claims are fine, and checking each receipt by hand is slow. Here is how to let a model compare the photo with the claim and the policy, approve the clear cases, and send the rest to a person.
Most expense claims are fine. Someone took a client to dinner, paid $244.80, photographed the receipt and claimed $244.80. A finance team still has to look at each one, because the few that aren't fine look exactly like the ones that are until you compare the photo with the claim: a total that doesn't match, a receipt from a hardware store filed as a client meal, alcohol above the limit with no approval.
That comparison is a decision, and it's a good fit for automation if you keep a person on the cases that need judgment. This post covers what to check, the tools that can do it, and how to set it up so the clear claims go through and the unclear ones reach a reviewer.
What a receipt check actually asks
Write the checks down as questions before picking a tool. A typical policy turns into four or five:
- Does the receipt total match the claimed amount?
- Is this the kind of receipt the category says it is (a restaurant for a client meal, a hotel for lodging)?
- Is the total over the policy limit?
- Is there a line the policy treats separately, such as alcohol above $50?
- Given all of that, what happens next: approve, send to a manager, or ask the employee for a better photo?
Each of those has a short, fixed set of answers. That matters for the tooling, because you want an answer you can branch on, not a paragraph.
The options
Expense platforms with receipt scanning. If your company already uses an expense tool, check what it does first. Many read the merchant, date and total from a photo and flag mismatches against the claim. When the built-in rules cover your policy, use them: nothing to build or run.
OCR plus rules. Extract the text with an OCR service or library, find the total with a pattern, compare numbers in code. Fully explainable and cheap per receipt. It breaks on the receipts people actually photograph: crumpled paper, a tip written by hand, a total that says "Amount due" on one receipt and "TOTAL EUR" on the next. Every new layout is another rule.
A general multimodal chat model. Send the photo and the policy, ask for a verdict, parse the reply. It reads messy receipts well and follows a policy written in plain language. You get free text back, so you need a strict output format and a parser, and you get no reliable measure of how sure it is, which is the thing you need to decide what a person should see.
A decision model that reads images. You send the claim and the policy as state, attach the photo, and ask your questions with your own options. You get a probability for every option of every question and nothing else. Saina Helm 2 Omni is one of these, with open weights you can run yourself.
None of these settles accuracy for your receipts in advance. Whatever you pick, test it on a few hundred past claims where you know the right answer.
Setting it up with a decision model
Put the claim and the policy in the state, attach the receipt, and ask every check in one request. With Saina Helm 2 Omni every question sees the image and the state, and they are answered together. This is the request behind the Receipt check preset in the Studio playground (illustrative):
{
"model": "saina-helm-2-omni-0.8b",
"mode": "decision",
"threshold": 0.8,
"state": {
"employee": "J. Ortiz",
"claimed_usd": 244.80,
"category": "Client dinner",
"policy": "Client meals are reimbursable up to $300 with an itemized receipt. Alcohol over $50 needs manager approval."
},
"images": ["data:image/png;base64,iVBORw0KGgo..."],
"questions": {
"total_matches": {"type": "yes_no", "question": "Does the receipt total match the claimed amount?"},
"restaurant": {"type": "yes_no", "question": "Is this a restaurant receipt?"},
"over_limit": {"type": "yes_no", "question": "Is the receipt total over the $300 limit?"},
"action": {
"type": "single_choice",
"question": "What should happen next?",
"options": {
"approve": "Approve",
"manager": "Send to a manager for approval",
"reject": "Reject"
}
}
}
}
POST it to https://api.saina.run/v1/ask with your API key, or to your own server if you self-host. Images go in as base64 data URLs (PNG, JPEG or WebP, up to 10 MiB each, up to 8 per request), so a receipt with two pages is two images in the same request.
Because mode is decision, each answer either carries a selection or comes back without one and a reason (below_threshold, below_margin or tie). That is the hook for the workflow.
Routing the result
Treat the model as the first reader, not the approver of record:
- Auto-approve only the clean case. Every check answered with a selection, the total matches, the category matches, under the limit, and
actionselectedapprove. Everything else goes to a person. - Never auto-reject. Rejecting an employee's claim is a decision about a person; the Omni model card puts final, unreviewed decisions with material consequences for a person out of scope. Use
rejectas a signal that moves the claim up the review queue, and let a person make the call. - Send abstentions to review with the reason. A claim where
total_matchescame backbelow_thresholdoften means a blurry photo. Asking the employee for a better one is cheaper than a reviewer squinting at it. - Show the reviewer the scores. "Total matches: no, 0.93" points the reviewer at the right line on the receipt.
What to watch for
Fine print. Images are resized to at most 1.6 megapixels before the model reads them, so very small print on a long receipt can be unreliable. Ask employees to photograph the receipt filling the frame, and split very long receipts into two photos.
Text inside the image is input too. A receipt is untrusted content. Someone could write an instruction on it, and text in an image can steer answers like text in the state. Keep the model's authority bounded: it can approve within policy, it can't change the policy, and anything unusual goes to a person.
Your own labels need testing. No labelled image benchmark is published for Helm 2 Omni yet, so there is no number to quote for receipts. Run your past claims through it, compare with what your reviewers decided, and set the threshold from that. The guide to image classification without training walks through building that test set.
Privacy. Receipts carry names, card digits and sometimes addresses. With open weights you can run the model on your own servers so the photos never leave them; the hosted API at api.saina.run runs the same server under a zero-retention policy.
Where this fits
If your expense tool's built-in checks cover your policy, keep them. A decision model earns its place when the policy has rules the tool can't express, when receipts come from several sources, or when you need the check inside an automation you already run. The pillar guide to classifying images and audio covers the same pattern for screenshots, charts and sounds.
To try it without writing code, create a free account and load the Receipt check preset in the Studio playground: it shows the request, the four answers and their scores, and copies the call as curl or Python. Runs are charged in credits, and the cost is shown first.