
How to Make an App Like Expensify

Building an expense management app is more involved than it looks. The UI is simple. The accounting logic underneath it is not.
This post covers what it actually takes to build something in the same class as Expensify: the data model, the OCR pipeline, the policy engine, the accounting integrations, and the approval workflows. If you're scoping this for the first time, use this as a technical checklist.
What Does an App Like Expensify Actually Do?
Expensify's core loop is: capture a receipt, extract the data, match it to a policy, route it for approval, and push it to an accounting system. That sounds straightforward. The edge cases are where the work lives.
A few things that look simple but aren't:
- Multi-currency expenses with mid-trip exchange rate fluctuations
- Per diem rules that vary by country, city, and employee grade
- Mileage reimbursement with different rates for personal vs company vehicles
- Duplicate detection across OCR-extracted receipts and manually entered claims
- Card transaction reconciliation when the card feed and the receipt don't match exactly
You also need to support corporate card feeds (typically via Visa's VCF or Mastercard's CDF formats), direct reimbursement via ACH or SEPA, and tax handling (GST, VAT, HST depending on geography).
What Does the Core Data Model Look Like?
Get the data model wrong early and you'll be migrating it under load six months later. Here's the schema that holds up.
Expenses and Reports
An Expense is a single line item: amount, currency, date, category, merchant, receipt reference, and policy flags. An ExpenseReport groups expenses into a submission unit. The report is what gets approved, not individual expenses.
Keep these as separate tables, not a nested document. You'll need to query expenses independently for things like duplicate detection, category analytics, and card reconciliation.
Policy and Approval Chain
Store policy rules separately from the expense record. A Policy has rules (spend limits by category, receipt required above a threshold, approval tiers by amount). An ApprovalChain is resolved at submission time based on the report's total, the submitter's org node, and any project or cost centre tagging.
Don't hardcode approval logic in application code. You'll change it constantly as clients onboard. Use a rule engine or a simple expression evaluator (something like SpEL or a JSON-based rule schema) so finance admins can edit policies without a deployment.
Receipt Storage
Receipts are binary blobs. Store them in object storage (S3 or GCS) with signed URLs for access. Keep the reference in the expense record, not the binary. Set a lifecycle policy to move receipts older than 90 days to cold storage. For compliance, most jurisdictions require 7 years of retention.
How Does the OCR Pipeline Work?
This is the part most teams underestimate. Raw OCR accuracy on receipts is around 85-90% with general-purpose models. That's not good enough for finance.
The Extraction Stack
Start with a base OCR layer. Google Document AI and AWS Textract both have receipt-specific models that outperform generic OCR on structured fields. Textract's AnalyzeExpense API returns structured fields (total, date, vendor, line items) rather than raw text, which cuts your parsing work significantly.
On top of that, you need a normalisation layer. Vendors write dates in 6+ formats. Amounts appear with and without currency symbols. Tax is sometimes included in the total, sometimes separate. You need rules and ML-based disambiguation for all of this.
The validation step is where you apply business logic: does the extracted total match the sum of line items? Is the currency consistent with the employee's location on that date? Does the merchant name match anything in your vendor master?
Plan for a human-review queue. Even at 95% automated accuracy, on 10,000 receipts a month you're routing 500 for manual review. Build that queue into the product from day one.
Duplicate Detection
Use a combination of fuzzy matching on amount + date + merchant and perceptual hashing on the receipt image itself. A submitter who photographs the same receipt twice will have near-identical images. A duplicate submitted across two expense reports will match on amount + date + vendor. You need both signals.
/// Not sure where to start?
Get the architecture before you commit
Tell us what you're building and we'll map the technical approach, stack, and rough timeline. No cost, no obligation, no sales call required.
What Does the Accounting Integration Layer Look Like?
This is the second place teams consistently get into trouble.
Expensify integrates with QuickBooks, Xero, NetSuite, Sage, and a handful of others. Each has a different data model. QuickBooks uses Bills and Bill Payments. NetSuite uses Vendor Bills and Journal Entries. Xero uses Spend Money transactions or bills depending on the flow.
Build an abstraction layer with a canonical internal format and per-integration adapters. The canonical format should map to general ledger concepts: debit account, credit account, amount, currency, date, description, dimension tags (cost centre, project, department).
Each adapter handles:
- Authentication (OAuth 2.0 for QuickBooks and Xero, token-based for NetSuite)
- Field mapping to the target system's schema
- Error handling and retry logic for transient API failures
- Webhook or polling for sync status
Rate limits are a real constraint. QuickBooks Online caps at 500 API calls per minute per company. Xero caps at 60 calls per minute. Batch your sync operations and queue them. Don't do expense-by-expense API calls.
The GL Coding Problem
Most accounting integrations fail not on the API layer but on GL coding. Every expense needs a chart of accounts code. Auto-suggesting the right code from expense category and merchant is an ML problem with training data that varies per customer. Start with a rule-based fallback (category X maps to account Y) and layer on prediction only when you have enough labelled data per customer.
How Do You Handle Approval Workflows?
A state machine is the right model here. An expense report moves through states: draft, submitted, pending_approval, approved, rejected, reimbursed, synced. Each transition has conditions and triggers notifications.
| Transition | Trigger | Side Effect |
|---|---|---|
| draft → submitted | Submitter action | Policy validation, approver resolution |
| submitted → pending_approval | System | Notification to first approver |
| pending_approval → approved | Approver action | Next tier resolution or final approval |
| approved → reimbursed | Finance action or auto-trigger | ACH/SEPA payment initiation |
| approved → synced | Accounting sync job | GL entry created in target system |
Multi-tier approvals are common. A report under £500 might need one manager. Over £5,000 it might need manager plus finance director. Implement approval chains as a linked list resolved at submission time. Don't re-resolve mid-approval; snapshot it.
Delegation matters more than you expect. Managers go on leave. Build substitution logic and an audit trail of who approved on whose behalf.
Conclusion
The honest answer is that building a full Expensify equivalent takes 12 to 18 months of focused engineering for a team of 6 to 8. Most companies should not do that. Where it makes sense is when you have specific compliance requirements, a geography or vertical Expensify doesn't serve well, or you need deep integration with an internal system that no off-the-shelf product touches.
If you're at the scoping stage, start with the data model and the policy engine. Those two decisions propagate everywhere else. Get them reviewed before you write application code.
Reach out to our team at Sodio if you want a technical scoping session. We've built financial tooling across several verticals and can tell you within a few hours whether your requirements justify a build.
FAQ
How long does it take to build an expense management app from scratch? A production-ready system with OCR, policy enforcement, multi-tier approvals, and at least two accounting integrations realistically takes 12 to 18 months for a team of 6 to 8 engineers. Cutting scope — for example, dropping card reconciliation or limiting to one accounting integration — can bring that to 8 to 10 months.
What's the hardest technical part of building an app like Expensify? The OCR pipeline and the accounting integrations are both harder than they look, but the real difficulty is the policy engine. Expense policies change constantly, vary by role and geography, and need to be editable by non-engineers. Getting that layer right without hardcoding business logic into application code is where most teams struggle.
Should I build or buy expense management software? Buy unless you have a compelling reason not to. Expensify, Concur, and Spendesk cover the mainstream case well. Build only if you have strict data residency requirements, a vertical with unique compliance needs, or deep integration requirements with internal systems that no vendor supports.
What tech stack works well for this type of app? The backend works well in Python or Node.js with PostgreSQL for transactional data. For the OCR pipeline, AWS Textract or Google Document AI handle receipt extraction. Background jobs (approvals, sync, notifications) fit well on a queue-based architecture using SQS or Pub/Sub. React or React Native covers the client side depending on your platform targets.
How do you handle multi-currency in expense reports? Store every expense in its original currency and convert to the base reporting currency at a fixed rate captured at the time of submission, not reimbursement. Use an exchange rate provider API such as Open Exchange Rates or ECB's reference rates. Never use live rates for accounting entries — you need a locked rate that matches what the accountant will see when they reconcile.
Have a project in mind? Contact Sodio Technologies to discuss your requirements and explore the right technology solution for your business.
/// Work with us
Talk to the engineers who'd build it
You'll get a technical scope, timeline and cost estimate from the people doing the work, not an account manager. In-house team, no subcontracting, since 2016.
