Free template · Build it with Claude, Cursor, Lovable or any AI app builder

Expense Reports template

Receipts pile up in email and reimbursements take weeks.

Expenses go on a report with their receipts, the manager approves it, and finance records the payment. Spending limits are checked when each expense is entered.

By Sam Claassen · Updated · 4 build phases · 24/24 acceptance checks passed

No.
014
Department
Finance
Build time
2 hrs
Difficulty
Intermediate
Team size
5–100 people
Replaces
Expensify
Your builder needs
A database and sign-in, plus a scheduled job, sending email and private file storage
Status
Build-tested Oct 15, 2026

Can you build your own expense reporting tool instead of paying for Expensify?

Yes, for out-of-pocket expenses at a company of 5 to 100 people. This blueprint specifies expense reports with receipts attached (photo or PDF), categories with spending limits, mileage at a company rate, approval by the submitter's manager, a finance queue for payment with a reference, and flags for over-limit, missing-receipt, old and duplicate expenses that must be explained before submitting. It is specified in four build phases with 24 acceptance checks. We built it by sending the 4 phase prompts to Claude in order, and all 24 checks passed in a real browser. It leaves out receipt scanning, company cards, travel booking, multiple currencies and accounting integrations.

ExpensesR-101BILL TOLee ParkBOSTON CLIENT VISITSUBMITTED OCT 9APPROVER SAMITEMAMOUNTTaxi to Acme46.20Dinner with Acme112.40Hotel, 1 night289.00Mileage, 38.5 mi26.95TOTAL$474.55Approve123

Built and tested from this blueprint

We sent the 4 build prompts below to an AI builder, in order, and checked every acceptance item in a real browser. Every refusal was confirmed on the server: the person saw the reason, and a direct request got an error with nothing changed. The test build ran as a full web app with a database, private receipt storage, an outbox and an hourly job. Where the build exposed a gap in the spec, we fixed the spec.

Download blueprint.md
Acceptance checks passed
24/24
Automated tests
59
Spec gaps found and fixed
9
Build phases
4

The full write-up of this build test, with every gap and what was not tested, is in the build-test report below.

Lee's draft R-101 ($474.55): the dinner flagged Over limit and needing an explanation, the hotel flagged Missing receipt.
Finance: every report with its status and next step, from draft to paid with its payment reference.
Finance: Spending for September, by category and by person, with monthly totals.
Settings: receipts required from $75, mileage at $0.70 a mile, the 90-day age flag, reminders after 3 days and category limits.
On a phone, each expense is a card with its flags; Take photo opens the camera for a receipt.

Watch the build test

We gave Claude one Markdown spec for small-team expense reports and had it build the tool in four phases. It passed all 24 acceptance checks in a real browser, along with 59 automated tests, and exposed 9 gaps in the spec, from a seed that loads relative to today while the checks used fixed dates to two rules that named different people who can see a receipt and an approver change with no event, email or comment rule. What passed, what broke, what we didn't test, and when Expensify is still worth paying for. 2:35.

Transcript

Here's how to reverse engineer Expensify from a written spec. We gave Claude one Markdown file, and checked twenty-four things in a real browser. Here's what passed, and what broke.

The spec copies the part of Expensify a small team uses to pay people back. Expenses with their receipts. Category limits, and mileage at a company rate. One approval, by your manager. And a finance queue, where every payment gets a reference.

The core rule is the report's lifecycle. A report starts as a draft. Submitting sends it to your manager. Approved reports go to finance, and reimbursed is final. Before you can submit, every flag needs an answer. A missing receipt blocks the report, and an expense over its limit needs an explanation.

Claude built it in four phases, one prompt each. The foundation, with sign-in, people and settings. Reports, expenses and flags. Approval and payment. Then reminders and spending. After every phase, the acceptance checks ran in a real browser, from fresh seed data.

Here's what it built. Lee's draft report flags a dinner over the meals limit, and a hotel with no receipt. Finance sees every report, with its status and next step. Spending adds it all up, by category and by person. Settings hold the receipt threshold and the mileage rate. And on a phone, every expense is a card, and Take photo opens the camera.

In the final run, on a fresh database, all twenty-four checks passed, along with fifty-nine automated tests.

But the more useful result is what broke. The build exposed nine gaps in the spec. Places where Claude had to guess. The seed data loads relative to today, but the checks used fixed dates. Two parts of the spec named different people who can see a receipt. And changing a report's approver had no event, no email, and no comment rule. Every gap is now fixed in the spec.

Passing every check isn't the same as production-ready. We didn't test real sign-in, sending email, the hourly job on a real scheduler, a real phone camera, or hosting. Those are yours to check before a team relies on it.

So is it worth building? Expensify's Submit plan is free, with receipt scanning and mileage. Reimbursements and approvals come with Collect, at five dollars per member per month. If you want receipt scanning or company cards, use Expensify. If you reimburse out-of-pocket spending for a small team, you mostly need receipts, one approval, and a record of payment.

The spec is free. The data model, twelve rules, seed data, four phase prompts, and all twenty-four checks, with the nine fixes already in. Find it at personal software factory dot com.

Made with AI from the real test-build screenshots. Voice: Kokoro (open-source text to speech). Music: "Chill Wave" by Kevin MacLeod (incompetech.com), licensed under Creative Commons Attribution 4.0.

Build it or buy Expensify?

Expensify's Submit plan is free for all members. Collect, its small business plan with reimbursements and approvals, is $5 per member per month. Control has custom pricing "as low as $9 per active member/month". Source: expensify.com/pricing, checked October 2026.

What this blueprint keeps

Reimbursable expenses with receipts (photo or PDF upload), categories with per-expense limits, mileage at a company rate, reports that move from Draft to Submitted to Approved to Reimbursed, retract by the submitter, reject with a comment, approval by the submitter's manager, a finance queue for payment with a reference, policy flags (over limit, missing receipt, old expense, possible duplicate) that need an explanation before submitting, reminders to slow approvers, a spending summary, and CSV export for your accounting system.

What it leaves out

Receipt scanning (v1 asks the employee to type the merchant, date and amount; the receipt is attached as proof), corporate and company cards and card feeds, travel booking, multiple currencies and exchange rates, per diems, tax reclaim, bill pay and invoicing, direct bank payments (finance pays through its usual bank run and records the reference here), accounting and ERP integrations (v1 exports CSV), multiple approval flows, budgets and SSO. These matter to companies with cards and travel programs. A team of 5 to 100 people reimbursing out-of-pocket spending needs receipts, one approval and a record of payment. If you want automatic receipt scanning or company cards, Expensify's free and $5 plans cover them.

Build it if

  • Receipts arrive by email and someone retypes them into a spreadsheet.
  • You reimburse out-of-pocket spending and pay through your usual bank run.
  • You want your spending limits and approval rules checked at entry, not at month end.
  • You want reimbursement records in your own database with a clean CSV export.

Buy Expensify if

  • You want receipts scanned automatically instead of typed.
  • You use or want company cards, card feeds and reconciliation.
  • You need travel booking, per diems or multiple currencies.
  • You want direct integration with QuickBooks, Xero or an ERP, or reimbursements paid from the app.

The blueprint

Paste the whole spec into your AI builder, then send each prompt in the Build plan section one at a time. Check each phase before moving on.

How to use this file: paste this whole document into your AI builder (Claude, ChatGPT, Cursor, Lovable, Replit, Type) as context. Then send the build prompts in section 15 one at a time. Do not move to the next phase until the acceptance checks for the current phase pass.


1. The problem

"Receipts pile up in email and reimbursements take weeks."

People pay for things on behalf of the company: a taxi to a client, a dinner, a hotel night, a stock photo, the miles they drove. The receipts go to finance by email, someone types them into a spreadsheet, a manager approves in a reply nobody can find later, and the employee waits weeks to be paid back without knowing where their money is.

This blueprint puts every out-of-pocket expense on a report with its receipt. A report goes to the submitter's manager for approval, then to finance to be paid, and at every step everyone can see its status. Spending limits are checked when the expense is entered, not discovered at month end.

2. What the real product does

Research basis: Expensify's pricing page (expensify.com/pricing) and its help article "Create and Submit Reports" (help.expensify.com/articles/new-expensify/reports-and-expenses/Create-and-Submit-Reports), October 2026.

What we verified:

  1. Plans and price. Submit is free for all members: receipt scanning, mileage tracking, expense reports, categories and tags, custom distance and tax rates, personal card import. Collect is $5 per member per month: reimbursements, corporate card management, expense and travel approvals, travel booking, and QuickBooks and Xero integrations. Control is custom pricing "as low as $9 per active member/month": multiple approval flows, custom expense rules, ERP and HR integrations, SAML SSO, custom reporting and budgeting.
  2. Reports. An expense report is created as a draft and expenses are added to it. Submitting moves it into the approval workflow; without one, the button says "Mark as done". Expenses that are on hold when a report is submitted move to a new draft report.
  3. Retract and reject. The person who submitted a report can retract it, which moves it back to Draft so expenses can be added, removed or edited. A workspace admin can submit a report on a member's behalf and can reject a report to request changes.
  4. Beyond reports. The Expensify Card, travel booking, bill pay and invoicing, chat on expenses, delegated access, and accounting and ERP exports.

What this blueprint keeps

Reimbursable expenses with receipts (photo or PDF upload), categories with per-expense limits, mileage at a company rate, reports that move from Draft to Submitted to Approved to Reimbursed, retract by the submitter, reject with a comment, approval by the submitter's manager, a finance queue for payment with a reference, policy flags (over limit, missing receipt, old expense, possible duplicate) that need an explanation before submitting, reminders to slow approvers, a spending summary, and CSV export for your accounting system.

What this blueprint deliberately leaves out

Receipt scanning (v1 asks the employee to type the merchant, date and amount; the receipt is attached as proof), corporate and company cards and card feeds, travel booking, multiple currencies and exchange rates, per diems, tax reclaim, bill pay and invoicing, direct bank payments (finance pays through its usual bank run and records the reference here), accounting and ERP integrations (v1 exports CSV), multiple approval flows, budgets and SSO. These matter to companies with cards and travel programs. A team of 5 to 100 people reimbursing out-of-pocket spending needs receipts, one approval and a record of payment. If you want automatic receipt scanning or company cards, Expensify's free and $5 plans cover them.

3. Scope of v1

  • One company, one currency (USD). Money is stored as integer cents. An expense paid in another currency is entered at the US dollar amount on the card or bank statement.
  • Reimbursable (out-of-pocket) expenses only.
  • One approval step (the submitter's manager), then payment by finance.
  • One team time zone (Settings; default America/New_York) decides what "today" is. Expense dates are calendar dates.
  • Receipts are private files: JPG, PNG or PDF, up to 10 MB each, stored in the platform's private file storage.
  • Email notifications through an outbox. If no email provider is configured, emails are logged, not sent.
  • Web app that works on a phone; taking a photo of a receipt on a phone must work.

4. Roles

Role Who Can do
Employee Everyone Create reports and expenses for themselves, submit, retract before approval, see their own reports and receipts.
Approver A person who is someone's manager (Person.manager_id) Approve or reject reports submitted by their direct reports, and see those reports and receipts. Cannot approve their own report.
Finance People with is_finance See every report and receipt, approve reports routed to them (R7), mark approved reports reimbursed, reject at the payment step, manage people and settings, export CSV, read the outbox.

The server enforces all of this, for every action and every receipt download.

5. Expense reports data model

All IDs are strings. Timestamps are UTC. "Date" means a calendar date in the team time zone.

Person

  • id, name (required), email (required, unique, stored lowercase)
  • manager_id (nullable, references Person; never the person themselves, no loops)
  • is_finance (boolean, default false), active (boolean, default true)

Settings (one row)

  • company_name, time_zone (default America/New_York)
  • receipt_required_cents (default 7500: a receipt is required for expenses of $75.00 or more)
  • mileage_rate_cents_per_mile (integer, default 70; set this to your company's rate)
  • max_age_days (default 90: expenses older than this are flagged)
  • reminder_days (default 3)

Category

  • id, name (unique), limit_cents (nullable: no limit), requires_receipt (boolean, default true), is_mileage (boolean; exactly one category, "Mileage"), active

Report

  • id, number (sequential, starting at 101, shown as R-101), title (1 to 100 characters), owner_id
  • status: draft | submitted | approved | reimbursed
  • approver_id (set at submission, R7)
  • submitted_at, approved_at, reimbursed_at (nullable)
  • payment_reference (nullable, 1 to 100 characters; set when reimbursed), paid_on (nullable date)
  • version (integer, goes up on every change)

Expense

  • id, report_id, category_id
  • date (date; not in the future), merchant (1 to 100 characters; for mileage: the trip, e.g. "Office to Acme, return")
  • amount_cents (integer, 1 to 1,000,000; for mileage, computed by R5)
  • miles_tenths (nullable integer; mileage only, 1 to 20,000, i.e. 0.1 to 2,000 miles), rate_cents_per_mile (nullable; copied from Settings when the expense is created)
  • description (optional, max 300), explanation (the note required when the expense has a flag, 5 to 300 characters)
  • receipt_file (nullable reference to a private file), created_at

Comment

  • id, report_id, author_id, text (1 to 1,000), created_at

Event (append-only, never edited or deleted)

  • id, report_id, actor_id (nullable for the system), type: created | expense_added | expense_edited | expense_removed | submitted | retracted | approved | rejected | reimbursed | reminded | commented | reassigned
  • data (JSON, nullable), created_at

Notification (outbox)

  • id, kind, to_email, report_id, subject, body, status: pending | sent | logged | failed, error, created_at, sent_at

6. Amounts, mileage and flags

  • R1. Money. Integer cents everywhere. A report's total is the sum of its expenses. Display with thousands separators and two decimals ($1,240.00).
  • R5. Mileage. amount_cents = miles_tenths × rate_cents_per_mile ÷ 10, rounded half up to the cent. The rate is copied from Settings when the expense is created and never changes afterwards, so changing the company rate never changes existing expenses.
  • Flags. Each expense is checked when it is saved and again when the report is submitted:
    • Over limit: the category has a limit and amount_cents is greater than it.
    • Missing receipt: the category requires receipts, amount_cents is at least receipt_required_cents, and no receipt is attached.
    • Old expense: the date is more than max_age_days before today.
    • Possible duplicate: another expense of the same person (in any report that is not reimbursed more than 90 days ago) has the same date, the same amount and the same merchant, ignoring case and spaces.
  • Flags are not stored. They are computed from current data whenever an expense or report is shown, and enforced at submission. Possible duplicate flags every matching expense, wherever it is. For a report that has been submitted, Old expense is judged against the date it was submitted.
  • R4. Flags need an explanation. A report cannot be submitted while any expense has a Missing receipt flag ("Attach a receipt for Hotel, 1 night ($289.00)."). Every other flag needs the expense's explanation filled in before submitting ("Explain why Dinner with Acme is over the Meals limit of $75.00."). Approvers see every flag and explanation.

Worked examples (seed data in section 14; today is Tue Oct 13, 2026)

Expense (Lee Park, R-101) Category Amount Flags
Taxi to Acme, Oct 8 Travel $46.20 none (under $75, no receipt needed)
Dinner with Acme, Oct 8 Meals (limit $75.00) $112.40, receipt attached Over limit: needs an explanation
Hotel, 1 night, Oct 8 Lodging (limit $300.00) $289.00, no receipt yet Missing receipt: blocks submitting
Office to Acme, return, Oct 9 Mileage at $0.70 38.5 miles = $26.95 none
Total $474.55
  • Mileage: 385 tenths × 70 ÷ 10 = 2,695 cents = $26.95.
  • If Lee adds another "Taxi to acme" for $46.20 dated Oct 8, both taxi expenses show Possible duplicate.
  • An expense dated Jul 1, 2026 is 104 days old on Oct 13 and shows Old expense.

7. Approval routing

  • R7. Who approves. At submission the approver is the owner's manager. If the owner has no manager, or the manager is inactive, the approver is the first active finance person by name who is not the owner. If there is nobody, submitting is refused: "No one can approve this report. Ask finance to set your manager." Nobody ever approves their own report.
Owner Manager Approver
Lee Park Sam Okafor Sam Okafor
Jo Park Maya Chen Maya Chen
Sam Okafor Dana Ruiz Dana Ruiz
Dana Ruiz none Ana Lima (the other finance person)

8. Lifecycle

Action Who From To Side effects
Create Employee (none) draft Number assigned; event created
Add, edit or remove expense Owner draft draft Flags recomputed; events
Submit Owner, or finance on the owner's behalf draft submitted Needs at least one expense and R4; approver set by R7; approver emailed
Retract Owner (not finance) submitted draft Clears approver and submitted_at; approver emailed that it was withdrawn
Approve The report's approver submitted approved Approver must not be the owner; owner emailed; it joins the finance payment queue
Reject The report's approver submitted draft Comment of 5 to 300 characters required; owner emailed with the comment
Reject at payment Finance approved draft Comment required; owner and approver emailed; the report must be approved again after resubmitting
Mark reimbursed Finance approved reimbursed paid_on (today or earlier) and payment_reference required; owner emailed; final
Reassign Finance submitted submitted Comment of 5 to 300 characters required; the approver changes; event reassigned; new approver emailed

Reimbursed is final. Any other move is refused. Only draft reports can be edited.

9. Expense reports business rules

  • R2. Expense validation. Date not in the future; merchant 1 to 100 characters; amount between $0.01 and $10,000.00; an active category; receipts must be JPG, PNG or PDF, at most 10 MB, and are checked by content, not just the file name.
  • R3. Receipts are private. A receipt is served only through an authenticated route to the people who can see its report (R8). Anyone else, or a signed-out request, gets "Not found". Receipt links are never public URLs.
  • R6. Submitting. A report needs at least one expense, no Missing receipt flags and an explanation on every other flagged expense.
  • R8. Visibility. A person can see a report, and its receipts, if they own it, are its current approver, are the owner's manager, or are finance. Anyone else gets "Not found" for a report URL.
  • R9. Stale changes. Approve, reject and reimburse check the report's version; if it changed after the page was loaded, the action is refused with "This report changed. Reload to see the latest version."
  • R10. People. A manager cannot be the person themselves or create a loop (A manages B manages A). Deactivating a person who is the approver of a submitted report is refused until finance reassigns it (finance can change the approver of a submitted report, with a comment).
  • R11. Events. Every successful action writes the event in section 8; events are never edited or deleted.
  • R12. Text. Merchant names, descriptions, explanations and comments are rendered as text, never as HTML.

10. Expense reports screens

  1. My reports (home). Each report with number, title, total, status and the next step ("Waiting for Sam Okafor", "Approved, waiting for payment", "Paid Oct 16, reference ACH-1016"). New report button.
  2. Report. Title, status, approver, total, and the expenses table (date, category, merchant, amount, receipt, flags with their explanation). Add expense (with "Mileage" switching the amount field to miles), receipt upload with a phone camera option, comments, and the activity log. Actions by role and status: Submit, Retract, Approve, Reject, Mark reimbursed.
  3. To approve. Submitted reports waiting on me, oldest first, with total and flag count.
  4. Finance: Payments. Approved reports waiting to be paid, grouped by employee, with totals; select several and Mark reimbursed with one paid_on date and reference; Export CSV of the selection.
  5. Finance: All reports. Every report with filters by status, person and month. Month always means the month of the expense date: a report appears under a month when any of its expenses is dated in that month.
  6. Finance: Spending. Totals of reimbursed and approved expenses by category and by month (last 6 months), and by person for the selected month; each expense counts in the month of its date.
  7. Admin: People (manager, finance flag, active) and Admin: Settings (receipt threshold, mileage rate, max age, reminder days, categories with limits). Finance only.
  8. Admin: Outbox.

11. Notifications

Trigger Recipient Subject
Submit Approver "To approve: R-101 Boston client visit ($474.55) from Lee Park"
Retract Approver "Withdrawn: R-101 Boston client visit"
Approve Owner "Approved: R-101, waiting for payment"
Reject (either step) Owner (and the approver, at the payment step) "Changes requested on R-101" (with the comment)
Mark reimbursed Owner "Paid: R-101 ($474.55), reference ACH-1016"
Reminder (section 12) Approver "Reminder: R-102 has been waiting 4 days"
Reassign New approver "To approve: R-102 October software ($204.00) from Jo Park"

Write every email to the outbox first, then send it. With no provider configured, set status logged. Failed sends are retried. A notification failure never blocks the action that caused it.

12. Scheduled job and time

An hourly job (protected by a CRON_SECRET header) does two things:

  1. Reminders. N is the number of whole 24-hour days since submitted_at, by the app clock. For each submitted report with N at least reminder_days, with no reminder yet for that submission, email the approver once and write a reminded event.
  2. Retry failed notifications.

All time reads go through one function that adds the development clock offset.

13. Edge cases

  • A manager leaves while a report waits for them: deactivation is refused until finance reassigns the report (R10).
  • An employee's manager changes after they submit: the report keeps its approver; the next submission uses the new manager.
  • The mileage rate changes: existing expenses keep their rate (R5); new ones use the new rate.
  • An expense is entered twice by mistake: Possible duplicate flags both, and the employee removes one before submitting or explains why they are different.
  • Finance marks a report reimbursed by mistake: reimbursed is final; record a correcting comment and handle it in the next payment run. (Reversals are a variation.)
  • A receipt photo is very large: refused above 10 MB with "Receipts can be up to 10 MB."

14. Seed data

Settings: company "Fernway", time zone America/New_York, receipt threshold $75.00, mileage rate $0.70 per mile, max age 90 days, reminder after 3 days.

Categories: Travel (no limit), Meals (limit $75.00), Lodging (limit $300.00), Software (limit $200.00), Office supplies (limit $150.00), Other (no limit), Mileage (no limit, no receipt).

People: Dana Ruiz (finance, no manager), Ana Lima (finance, manager Dana Ruiz), Sam Okafor (manager Dana Ruiz), Maya Chen (manager Dana Ruiz), Lee Park (manager Sam Okafor), Jo Park (manager Maya Chen). Everyone is active. Emails are first name at fernway.com (dana@fernway.com, ana@fernway.com and so on).

Reports (T is when the seed is loaded, here Tue Oct 13, 2026). Every date in this section is relative to T: load it as the same number of days before T (Oct 8 is T-5 days). The worked examples and checks use these example dates; on another day, use the matching relative dates.

Report Owner Status Expenses Total
R-101 Boston client visit Lee Park Draft The four expenses in section 6 (dinner receipt attached, hotel receipt missing, no explanations yet) $474.55
R-102 October software Jo Park Submitted T-4 days, approver Maya Chen, no reminder yet Figma plugin, Oct 2, Software, $24.00; Stock photos, Oct 5, Software, $180.00, receipt attached $204.00
R-103 Team offsite supplies Sam Okafor Approved T-1 day by Dana Ruiz Notebooks and markers, Oct 6, Office supplies, $132.80, receipt attached $132.80
R-104 Design conference Maya Chen Reimbursed Sep 30, reference ACH-0930 Conference ticket, Sep 10, Other, $890.00; Hotel, 2 nights, Sep 11, Lodging, $350.00 (over limit, explained "Two nights in one charge, $175 a night"); both with receipts $1,240.00

R-103 was submitted T-2 days. R-104 was submitted Sep 14 (T-29 days), approved Sep 15 (T-28 days) by Dana Ruiz, and marked reimbursed by Dana Ruiz.

Use small placeholder images as seed receipts.

15. Expense reports build plan

Send these prompts one at a time after pasting this file. Each phase ends with checks you can run by clicking through the app. Start the checks of Phases 1, 2 and 4 from a fresh load of the seed (the reset button); Phase 3's checks continue from the end of Phase 2's. Checks within a phase run in order on the same data.

Phase 1: Foundation

Using the Expense Reports blueprint above, build Phase 1 only.

1. Set up the data model from section 5 exactly, with a real database and private file storage for receipts.
2. Add sign-in with the platform's built-in auth or Google sign-in limited to our company email domain. A signed-in email must match an active Person; anyone else sees "Ask finance to add you."
3. Build Admin: People and Admin: Settings with categories (section 10, screen 7). Only finance can use them; enforce the manager rules in R10 (no self-manager, no loops) on the server. The deactivation rule comes with reassigning in Phase 4.
4. Load the seed data from section 14, with dates relative to today.
5. Add a "sign in as" switcher, a "move the clock forward" control and a "reset to seed data" button, all visible only in development.

Do not build reports or expenses yet. When you're done, list what you built and anything in the spec you had to interpret.

Acceptance checks

  • Signed in as Dana, Admin: Settings shows a $75.00 receipt threshold, a mileage rate of $0.70 and the seven categories with Meals limited to $75.00.
  • Signed in as Lee, Admin: People says "Only finance can open this page." and a direct request to change settings is refused.
  • Setting Dana Ruiz's manager to Lee Park is refused because it would create a loop (Lee reports to Sam, who reports to Dana).
  • Changing the mileage rate to $0.72 and the Meals limit to $80.00 saves both, and Admin: Settings shows the new values after a reload.

Phase 2: Reports, expenses and flags

Using the Expense Reports blueprint above, build Phase 2 only.

1. Build My reports and the Report screen (section 10, screens 1 and 2) for employees: create reports, add, edit and remove expenses with receipt upload (including a phone camera option) and mileage.
2. Implement R1, R2, R5, the four flags and R4 (section 6), recomputed on the server on every save and again at submission.
3. Implement Submit and Retract (section 8) with R6, R7 and R8, and serve receipts only as R3 describes.
4. Write the Submit and Retract emails to the outbox (section 11) and build Admin: Outbox.

When you're done, list what you built and anything in the spec you had to interpret.

Acceptance checks

  • Signed in as Lee, R-101 shows a total of $474.55, the mileage expense as 38.5 miles = $26.95, an Over limit flag on the dinner and a Missing receipt flag on the hotel.
  • Submitting R-101 is refused with "Attach a receipt for Hotel, 1 night ($289.00)."; after uploading a receipt it is refused with the message asking to explain the dinner.
  • After adding the explanation "Dinner with three people from Acme", R-101 submits, its approver is Sam Okafor, and the outbox has "To approve: R-101 Boston client visit ($474.55) from Lee Park".
  • Adding a second "Taxi to acme" for $46.20, dated the same day as the seed taxi (Oct 8 when T is Oct 13), to a new draft report flags both taxi expenses as Possible duplicate; then remove the second taxi.
  • Uploading a 12 MB file, or a text file renamed receipt.jpg, is refused.
  • Signed in as Jo, opening R-101 returns Not found, and a direct request for its hotel receipt returns Not found.
  • Lee retracts R-101: it returns to Draft, and the outbox has "Withdrawn: R-101 Boston client visit" for Sam.
  • On the new draft report from the taxi check, an expense dated tomorrow is refused, and one dated more than 90 days ago (Jul 1, 2026 when T is Oct 13) shows Old expense.
  • After the mileage rate is changed to $0.72 in Settings, R-101's mileage expense still shows $26.95, and a new 10-mile expense on the new draft report shows $7.20.

Phase 3: Approval and payment

Using the Expense Reports blueprint above, build Phase 3 only.

1. Build To approve, Finance: Payments and Finance: All reports (section 10, screens 3 to 5).
2. Implement Approve, Reject, Reject at payment and Mark reimbursed (section 8) with R7 and R9, enforced on the server, and the related emails (section 11).
3. Add CSV export from Finance: Payments: report number, employee, email, approved date, total, and one line per expense with date, category, merchant and amount.

When you're done, list what you built and anything in the spec you had to interpret.

Acceptance checks (continue from the end of the Phase 2 checks)

  • Signed in as Maya, To approve lists R-102 ($204.00) from Jo Park; approving it emails Jo "Approved: R-102, waiting for payment" and it appears in Finance: Payments.
  • After Lee submits R-101 again, signed in as Sam, rejecting it without a comment is refused; with the comment "Please split the hotel onto its own report" it returns to Draft and Lee is emailed the comment.
  • Signed in as Dana, submitting a report of her own routes it to Ana Lima, and Dana has no Approve button on it; a direct approve request from Dana is refused.
  • Exporting R-103 from Finance: Payments downloads a CSV with its one expense line and the total $132.80.
  • Marking a report reimbursed without a reference is refused.
  • In Finance: Payments, Dana selects R-102 and R-103 and marks them reimbursed on today's date with reference ACH-1016; both show "Paid", their owners are emailed, and they no longer appear in Payments.
  • Lee submits R-101 again. With it open in two windows as Sam, approving it in one and then rejecting it in the other is refused with "This report changed. Reload to see the latest version."

Phase 4: Reminders and spending

Using the Expense Reports blueprint above, build Phase 4 only.

1. Build the scheduled job in section 12, with a development-only "run job now" control that uses the development clock.
2. Build Finance: Spending (section 10, screen 6).
3. Let finance change the approver of a submitted report with a comment, and enforce the deactivation rule in R10.

When you're done, list what you built and anything in the spec you had to interpret.

Acceptance checks (reset to seed data first)

  • Running the job emails Maya "Reminder: R-102 has been waiting 4 days" once; running it again sends nothing new.
  • Deactivating Maya Chen is refused while R-102 waits for her; after finance reassigns R-102 to Dana with a comment, deactivation succeeds.
  • Finance: Spending for the month of R-104's expenses (September when T is Oct 13) shows Other $890.00 and Lodging $350.00 from R-104.
  • Signed in as Lee, Finance: Spending says "Only finance can open this page."

16. Variations

  • Receipt scanning that suggests the merchant, date and amount for the employee to confirm.
  • Per diems by city.
  • A second approval by finance for reports over a set total.
  • Multiple currencies with the exchange rate on the expense date.
  • Direct payment through your bank's API, with the reference filled in automatically.
  • Export to QuickBooks or Xero.
  • Reversing a reimbursed report with a correcting entry.

17. Build notes from our test build

We built this blueprint by sending the four phase prompts above, in order, to an AI builder (Claude, working in Type), then checked every acceptance item in a real browser. Where a check says something is refused, the person saw the reason on screen and a direct request got an error with nothing changed.

Result: 24 of 24 acceptance checks passed in one final browser run on a fresh database, plus 59 automated tests. The seed was loaded relative to the day of the test (Oct 15), so dated checks used the matching relative dates.

Gaps the build found, now fixed in this spec:

  1. The seed is loaded relative to today, but the examples and checks used fixed dates. Section 14 now says how to read them, and the checks name the relative date.
  2. The seed people had no emails, and two reports had no submitted or approved dates. Section 14 now gives them.
  3. Phase 1 asked for the deactivation rule before reassigning existed. It now comes with reassigning in Phase 4.
  4. Only Phase 4 said to reset the seed, and two Phase 2 checks didn't say which report to use. The build plan and the checks now say.
  5. It wasn't clear when flags are computed. Section 6 now says they are computed, not stored, and how the old-expense flag works after submitting.
  6. R3 and R8 named different people who can see receipts. Both now use one rule.
  7. "Month" wasn't defined for filters and spending. It is the month of the expense date.
  8. Changing a report's approver had no event, email or comment rule. Section 8 now has a Reassign action; the test build recorded it as a comment event.
  9. The reminder's "N days" wasn't defined, and two examples disagreed. Section 12 now defines it.

What we did not test:

  • Production sign-in. Google sign-in was built, but every check used the development "sign in as" switcher.
  • Sending email. Emails were logged in the outbox, not delivered.
  • The hourly job on a real scheduler. Its endpoint was called directly.
  • A real phone camera. Receipts were uploaded as generated images and PDFs.
  • Payments and accounting integrations, which are outside v1.
  • Hosting. The test build ran on SQLite on one server. For a hosted deploy, use a managed Postgres (or your platform's database) and the same schema.

Field report

Building an Expensify replacement with AI, 24 checks and 9 spec gaps

We had an AI builder build a small-company expense reporting tool from our spec, one phase at a time, and checked every acceptance item in a real browser. It passed all 24 checks on a fresh database and exposed 9 gaps in the spec, under the median for our builds. What it costs next to Expensify, how each gap was fixed, and what we did not test.

The short answer

Yes, for a company of 5 to 100 people reimbursing out-of-pocket spending. We gave Claude, working in Type, our Expense Reports blueprint and had it follow the four phase prompts in order. The final build passed 24 of 24 acceptance checks in one browser run on a fresh database, with 59 automated tests. It handles reports with receipts attached, category limits, mileage at a company rate, approval by the submitter's manager, a finance queue with payment references, policy flags that must be explained before submitting, reminders to slow approvers, a spending summary and CSV export.

The build exposed 9 gaps in the spec, places where the builder had to guess because the spec was silent, ambiguous or disagreed with itself. Across our twenty-two build tests the median is 14.5, so this one sat under it. Every gap is now fixed in the published spec.

What it is not: a tested production deployment. Real sign-in, delivered email, a real scheduler, a phone camera and hosting were not tested. The last section lists what was and wasn't checked.

What we built, and what we left out

The blueprint copies the part of Expensify a small company uses to pay people back. We read Expensify's pricing page and its help article on creating and submitting reports in October 2026, and kept the rules a team reimbursing out-of-pocket spending needs:

  • Four statuses, one way through. Draft, Submitted, Approved and Reimbursed. Only drafts can be edited. The owner can retract a submitted report, as Expensify allows; an approver can reject with a comment; Reimbursed is final.
  • Your manager approves, never you. The approver is the owner's manager. With no manager, or an inactive one, the first active finance person who is not the owner gets it. Nobody approves their own report, finance included.
  • Flags checked at entry, not at month end. Over limit, Missing receipt, Old expense and Possible duplicate are computed from current data whenever an expense is shown. A missing receipt blocks submitting; every other flag needs an explanation.
  • Mileage that doesn't drift. The company rate is copied onto each mileage expense when it is created, so changing the rate later never changes an existing expense. 38.5 miles at $0.70 is $26.95.
  • Receipts that stay private. JPG, PNG or PDF up to 10 MB, checked by content rather than file name, and served only to the owner, the approver, the owner's manager and finance. Everyone else gets "Not found".
  • A stale-change check. Approve, reject and reimburse compare the report's version and refuse if a colleague changed it first: "This report changed. Reload to see the latest version."

Left out on purpose: receipt scanning (the employee types the merchant, date and amount), company cards, travel booking, multiple currencies, per diems, tax reclaim, bill pay, direct bank payments, accounting integrations, multiple approval flows, budgets and SSO. If you need those, Expensify's plans cover them.

What it costs next to Expensify

Expensify prices per member per month. On the prices we saw on expensify.com/pricing in October 2026, the Submit plan is free for all members. Reimbursements and approvals, the part this blueprint replaces, are on Collect:

Team Expensify Collect ($5 per member per month)
10 members $600 a year
25 members $1,500 a year
50 members $3,000 a year

Control, with multiple approval flows and custom expense rules, has custom pricing "as low as $9 per active member/month".

The build has no per-seat price, but it isn't free: you pay for hosting, a database, private file storage for receipts, an email provider, and the time to build and look after it. At $5 a member, Expensify is already cheap; if you want receipts scanned automatically or company cards, buy it. Build when your approval and payment flow has to match your policy exactly, or when you want the history in your own database. Our build-or-buy guide has the full decision.

How the build went, phase by phase

The builder received the blueprint as one Markdown file and sent itself the four phase prompts one at a time. After each phase, that phase's acceptance checks ran in a real browser as the named person; Phases 1, 2 and 4 started from a fresh load of the seed data and Phase 3 continued from the end of Phase 2. After the last phase, all 24 checks ran again in one final pass. Every refusal was confirmed on the server, not just on screen.

  1. Foundation. The data model with private file storage, sign-in for development, Admin: People and Settings, the seed data, and the development switcher, clock and reset controls.
  2. Reports, expenses and flags. My reports and the Report screen, receipt upload with a phone camera option, mileage, the four flags, Submit and Retract, and the outbox.
  3. Approval and payment. To approve, Finance: Payments and All reports; Approve, Reject, Reject at payment and Mark reimbursed with the version check; CSV export.
  4. Reminders and spending. The hourly job, Finance: Spending, reassigning an approver, and the deactivation rule.

Each phase prompt ends with one sentence: "list what you built and anything in the spec you had to interpret." That sentence is what surfaced the gaps.

The 9 gaps, grouped

The full list, with each fix, is in the blueprint's build notes. They fall into three groups.

Seed data and checks that didn't line up (3 gaps)

The seed is loaded relative to the day of the test, but the examples and checks used fixed dates like Oct 8; section 14 now says how to read them and each check names the relative date. The seed people had no email addresses, which sign-in needs, and two seeded reports had no submitted or approved dates. And only Phase 4 said to reset the seed, while two Phase 2 checks didn't say which report to use.

Lesson: seed data is part of the spec, and so is the order the checks run in. Say when to reset and which record each check touches.

Rules that arrived in the wrong order, or in two versions (3 gaps)

Phase 1 asked for the deactivation rule before reassigning, which it depends on, existed in Phase 4; it now comes with reassigning. R3 and R8 named different sets of people who can see a receipt; both now point at one rule. The reminder's "N days" was never defined and two examples disagreed about it, so the builder picked one.

Lesson: when two sections describe the same behaviour, one of them will drift. Say it once and refer to it, and don't ask for a rule before the thing it depends on exists.

Behaviour the spec used but never defined (3 gaps)

The spec never said when flags are computed, so it was unclear whether a flag is stored or recomputed, and how Old expense behaves once a report is submitted. "Month" in the finance filters and spending report could mean the expense, submission or payment date; it is now the month of the expense date. And finance could change a report's approver with no event, email or comment rule, so the test build recorded it as a comment event; the lifecycle table now has a Reassign action with all three.

Lesson: for every rule, ask when it runs, which date it uses and what it writes to the log. See What AI app builders get wrong for the patterns across every build.

What we did not test

  • Production sign-in. Google sign-in was built, but every check used the development "sign in as" switcher.
  • Sending email. Emails were logged in the outbox, not delivered.
  • The hourly job on a real scheduler. Its endpoint was called directly.
  • A real phone camera. Receipts were uploaded as generated images and PDFs.
  • Payments and accounting integrations, which are outside v1.
  • Hosting. The test build ran on SQLite on one server. For a hosted deploy, use a managed Postgres, or your platform's database, and the same schema.

These gaps are not special to expense reports. We list what every build left untested in Is an AI-built internal tool ready for production?.

Build it yourself

The Expense Reports blueprint is free: one Markdown file with the data model, the 12 business rules, screens, seed data, the four phase prompts and all 24 acceptance checks, with the 9 fixes already in. Paste it into Claude, ChatGPT, Cursor, Lovable, Replit or Type and send the phases one at a time. Our Expensify alternatives page puts the two side by side. If you find a gap we missed, tell us on the About page and we'll credit it in the build notes.

Expense Reports: questions people ask

How does an expense report approval process work?

An employee adds expenses with receipts to a draft report and submits it. In this blueprint it goes to the employee's manager, who approves it or rejects it with a comment that sends it back to draft. Approved reports wait in a finance queue until finance pays them and records the payment date and reference. The submitter can retract a report until it is approved, and nobody can approve their own report.

When should a receipt be required?

Pick a threshold and apply it per category. This blueprint's default requires a receipt for any expense of $75 or more, except mileage, and a report cannot be submitted while an expense is missing one. Set the threshold and categories to match your own policy and your accountant's advice.

How do you calculate mileage reimbursement?

Miles times the company's rate per mile, rounded to the cent. With a rate of $0.70 per mile, a 38.5-mile trip is $26.95. The rate is saved on each mileage expense when it is created, so changing the company rate later never changes expenses already entered.

How do you catch out-of-policy or duplicate expenses?

Check every expense as it is entered. This blueprint flags expenses over their category's limit, older than 90 days, or with the same date, amount and merchant as another expense from the same person. Flagged expenses need a short explanation before the report can be submitted, and approvers see every flag with its explanation.

What data model does an expense tool need?

Person (with manager and a finance flag), Settings (receipt threshold, mileage rate, maximum age), Category (with an optional limit and whether a receipt is required), Report (number, owner, status, approver, payment date and reference), Expense (date, merchant, amount in integer cents, miles and rate for mileage, explanation, receipt file), Comment, an append-only Event log and a Notification outbox.

How should receipts be stored?

As private files that only the employee, their report's approver and finance can open, served through a signed-in route rather than a public link. This blueprint accepts JPG, PNG and PDF files up to 10 MB, checks the file contents rather than just the name, and lets employees take a photo on their phone.

How much does Expensify cost?

Expensify's Submit plan is free, Collect is $5 per member per month, and Control has custom pricing from $9 per active member per month (checked October 2026). Collect is the plan that adds reimbursements and approvals for a small business.

Get new blueprints by email

One email when a new blueprint is published. Nothing else.

More blueprints