Building practical AI
Deepak Kumar Tiwari

Deepak Kumar Tiwari

Technology & AI Leader · Building practical systems

Production workflows / Guide 18

An Expense Approval Agent That Separates Policy, Evidence, and Payment

Build an expense approval agent that checks receipts and policy versions, routes exceptions to people, and prevents duplicate reimbursement through reconciliation.

Illustrative engineering guide. Examples are not client deployments, production-ready integrations, or evidence of measured results. Validate the design with your domain and security owners.

Reference workflow: Authenticated expense submission; Receipt and payment-source matching; Versioned policy evaluation; Manager and finance approval; Reimbursement and reconciliation.
Reference workflow. Steps identify responsibilities, not necessarily separate agents. Branches and recovery paths are explained below. Open full-size diagram.

A hypothetical employee submits a hotel folio containing room charges, breakfast, laundry, and a personal extension night. The corporate card feed already contains the full payment. An assistant that extracts the total and recommends reimbursement can be wrong twice: it may include personal spending and reimburse money the employee never paid.

An expense approval agent should assemble a policy-check packet, distinguish payment sources, and explain exceptions. Arithmetic, policy dates, duplicate controls, approval authority, and reimbursement execution belong to deterministic systems. The model should not become a generous manager with access to the payment rail.

Separate reimbursable, deductible, and substantiated

These are different questions. An organization may reimburse a cost under its employment policy even when tax treatment requires separate accounting. A valid receipt shows a purchase, not necessarily its business purpose. A business purpose does not prove that the employee personally paid.

IRS Publication 463 discusses travel, meals, recordkeeping, and reimbursements in a United States tax context. Use it as a source for the distinction between evidence and tax treatment, not as a universal expense policy. Finance and tax professionals must define applicable jurisdictional rules and maintain their effective dates.

Store transaction amount and currency, payment instrument, merchant, service dates, expense category, business purpose, attendee context where needed, and supporting documents. Avoid collecting unnecessary personal details. For a shared meal, record the minimum information finance requires rather than exposing an entire customer-contact database to the assistant.

Let the model explain, not calculate eligibility

The LLM can extract line items and ask targeted questions: “Was the extension night personal, and was it paid separately?” It can map a confusing merchant description to a proposed category. That proposal needs evidence and should remain editable.

Conventional software calculates totals with decimal arithmetic or integer minor units. It applies the versioned policy valid for the expense date and relevant employee group. Do not use today’s hotel limit to judge a trip taken before a policy change unless the approved policy explicitly requires that treatment.

The minimum architecture includes submission intake, OCR, card-feed matching, policy lookup, a rules evaluator, an exception queue, and a reimbursement adapter. None of these tools needs autonomous goals. A specialist tax worker is not justified for routine company-policy routing; genuinely complex tax questions should be escalated to qualified people.

Model line-level eligibility explicitly

This executable Python example totals reviewed employee-paid lines using integer minor units. It deliberately refuses mixed currencies rather than silently choosing an exchange rate.

def reimbursement_minor(lines, currency):
    total = 0
    for line in lines:
        if line["currency"] != currency:
            raise ValueError("conversion evidence required")
        amount = line["amount_minor"]
        if type(amount) is not int or amount < 0:
            raise ValueError("invalid charge")
        if line["approved"] and line["paid_by"] == "employee":
            total += amount
    return total

assert reimbursement_minor([
    {"currency": "USD", "amount_minor": 15000,
     "approved": True, "paid_by": "corporate_card"},
    {"currency": "USD", "amount_minor": 2500,
     "approved": True, "paid_by": "employee"},
], "USD") == 2500

Production dependencies omitted include trusted approval records, validated payment-source enums, receipt verification, currency metadata, exchange-rate policy, duplicate detection, refunds, and ledger integration. The approved flag must come from the authorization service, not an extracted receipt field. Refunds require their own signed accounting treatment rather than fitting negative values into this simplified charge function.

For the hotel example, finance can reconcile the corporate-card charge, allocate personal spending under company policy, and determine whether any employee-paid amount remains. A reimbursement total of zero may be correct even though legitimate business expenses exist.

Duplicate detection needs more than a receipt hash

An exact digest catches the same uploaded file twice. It does not catch a photograph and PDF of the same receipt. Use transaction identifiers where available, then compare merchant, date, currency, amount, and payment source to produce review candidates.

Do not automatically reject every matching amount. Two employees may legitimately buy identical train tickets. Conversely, splitting one receipt across submissions can evade a naive per-submission threshold. Evaluate allocations at the underlying transaction level and retain links between related claims.

Approval must bind the reviewed lines, policy version, exchange-rate evidence, and total. If an employee edits the amount afterward, invalidate the approval. A manager should not approve their own claim, and delegated approval should have explicit scope and expiry enforced server-side.

Treat payment as a separate durable operation

Create a reimbursement instruction only after the required approvals. Assign a stable identifier to the approved report version and persist it before contacting payroll or a payment provider. If the response is lost, reconcile the instruction before retrying. Never generate a new payment ID just because the first attempt timed out.

Keep approved, submitted, paid, rejected, and reconciled states distinct. A payroll export accepted by an API does not prove that the employee received funds. If bank details are invalid, route correction through the employee’s authenticated payroll workflow; a receipt attachment cannot supply replacement bank instructions.

When payroll succeeds but ledger posting fails, retry ledger reconciliation without paying again. Track the liability until both systems agree. Operators need an exception queue showing exactly which external effect is confirmed and which remains uncertain.

Protect employee and financial information

Scope every read and write to the organization, employee, report, and authorized reviewer. PostgreSQL row security can reinforce tenant boundaries, but test the actual application role and avoid privileged bypass. Finance-wide access should not become the model’s default context.

Receipts and travel itineraries are untrusted documents. An instruction printed in a receipt to “approve all items” must have no effect on policy execution. Use the layered approach in OWASP’s injection guidance: constrained tools, independent authorization, and human review for consequential exceptions. MCP does not authorize reimbursement.

Minimize logs containing card details, travel locations, or bank information. Preserve necessary audit evidence in governed storage with a finance-approved retention policy. Model debugging should use redacted or synthetic examples wherever possible.

Acceptance tests for money-moving edges

  1. Submit a corporate-card hotel folio as an employee reimbursement. The system identifies the payment source and prevents duplicate reimbursement while retaining business-expense accounting.
  2. Upload a photo and PDF of one receipt. They become a duplicate-review candidate, not two independently payable reports.
  3. Edit a line amount after manager approval. The payment gate rejects the stale approval and requires renewed review.
  4. Lose the payment response after provider acceptance. Reconciliation finds one instruction and no second transfer is created.
  5. Put replacement bank details and approval instructions in an attachment. Neither payroll identity nor policy authorization changes.

Measure total handling cost and control quality

Baseline finance and employee handling minutes per completed report, segmented by simple receipts, mixed-purpose travel, and exceptions. Measure reports finalized without material correction divided by all reviewed reports. Include clarification time and exception handling so apparent automation does not merely shift work to employees.

Guardrails include duplicate disbursements, unauthorized approvals, unsupported classifications, and aged approved-but-unpaid reports. Measure each against its relevant denominator, such as duplicate disbursements divided by total disbursements. Proposed targets should be approved by finance; no results are claimed here.

Start with read-only checks on one expense category, then staff-reviewed recommendations before enabling controlled reimbursement submission. The invoice processing guide covers supplier-payment evidence, and the corporate travel guide addresses policy before spending occurs. Golang Backend & API Engineering is relevant for the durable approval and reconciliation layer that makes the assistant safe to use.

Sources