A junk hauling company gets a photo and a paragraph of text, and has to turn that into a price, a pickup slot and a route before anyone commits to anything. This app does the whole loop: an AI agent reads the photo and the description together and returns an estimate with an item breakdown, the customer books a weekday slot at an address, the backend geocodes it and calculates real driving distance from the warehouse, staff accept or decline from a dashboard, and the accepted job gets paid.
There is no separate frontend project, no coding agent, no plugin. The data model, the permissions, the AI agent, the automated logic, the payment wiring and every screen came out of one prompt pasted into Momen's in-product AI Copilot. Everything below was read back out of the generated project.
Try the live app · Clone it in the Momen editor


removal_request.status holds exactly one of five text values — quoted, scheduled, accepted, declined, paid — and each transition belongs to exactly one Actionflow. Nothing else writes to it.
That single constraint is what keeps a two-sided workflow from decaying into a pile of boolean flags. Every update that moves the status is filtered on the status it expects to find, so a flow can't fire from a state it wasn't designed for. Booking, for instance, is filtered to rows sitting in quoted or declined — which is the entire implementation of "a declined request can be fixed and resubmitted." No second flow, no is_resubmission column. It's the same move as picking a state counter over a scattered set of flags: let one column and a where clause carry the rule.
One request row accumulates the whole job as it moves: what the customer wrote and photographed, what the AI priced it at and why, where and when the pickup is, how far that is from the warehouse, and whatever the reviewing staffer noted. An accepted job grows a second row — the order — which is what the payment module binds to.
The modelling is deliberately ordinary. The decisions worth copying are in permissions, which come from roles rather than a column on the account table. Anonymous visitors can read nothing at all. A signed-in customer sees only rows that belong to them — enforced by the backend, not by which page you happen to be on. Staff see everything, including the photo and the address. And no role, customer or staff, can invoke the AI agent or reach the external APIs directly; those exist only inside the flows below.
That last line is the one that matters for anything charging money. Access is a property of the data, so a leaked query or a poked-at URL gets the same answer as the UI would.
The estimator is an AI agent on GPT-5.6, running low-temperature because pricing should be boring. It is told to inspect the image and treat the customer's words as supporting context, commit to one number rather than hedge with a range, explain itself with an item-by-item breakdown, and never invent something that isn't visible or described.
It answers in a defined shape — a price and a breakdown, each a named field the model is told how to fill — rather than in prose. So the result lands in the database directly, with nothing to parse and no regex waiting to break on the day the model phrases itself differently.
The photo genuinely reaches the model as an image, which is the entire feature. A price derived from "a few old chairs and some boxes" is a guess; a price derived from the picture is an estimate a business can stand behind. An image in, a judgement out is what turns a form into something worth filling in.
Actionflows own every write to status.

Create Request is deliberately thin: resolve the logged-in account, insert one row with status quoted and everything else empty. The quote is a separate step.
Estimate Price (AI) is asynchronous. It loads the request, calls the estimator, and writes the price and breakdown back. Multimodal inference takes long enough that blocking a screen on it would be the wrong design, so the page shows a pending state and picks the result up when it lands.
Book Pickup is the strict one, and its ordering matters. Two custom-code nodes run first: one parses pickup_date and throws unless it's a real calendar date on a weekday, the other rejects any time_slot outside the nine allowed values and names them in the error. Only then does the third node hit the network. Validating before spending two API calls is the cheap version of the same instinct that keeps a booking system from selling the same slot twice — decide what's legal before you commit to anything expensive.
Decide (Staff) saves the optional note, sets accepted or declined, and branches: on accept only, it inserts one order for the same customer at price_usd × 100, rounded, in USD, pending. Distance and duration are never recalculated here — they were computed once during booking and are read, not redone.

Both lookups are registered as third-party APIs in the project and called from inside the booking flow's final node. Neither is reachable from a browser.
Nominatim (GET nominatim.openstreetmap.org/search) converts the typed address into coordinates. It rejects any request arriving without a descriptive User-Agent, so the call sends one identifying the service. The code takes the first result and throws a readable error if the response is empty, non-2xx, or carries coordinates that don't parse.
OSRM (GET router.project-osrm.org/route/v1/driving/{coordinates}) turns two coordinate pairs into a real driving route. The origin is the warehouse, hardcoded — the prompt told the Copilot to treat company constants as constants rather than build a table for a single row that never changes. The code reads routes[0].distance and routes[0].duration, converts to miles at one decimal and whole minutes, and throws if the route is unusable rather than saving a request with a hole in it.
This node is the only place in the project where distance and duration are ever calculated, which is why the staff dashboard can display them with no risk of two code paths disagreeing.
Stripe is wired with order as the bound order table, and the customer pays from the request detail page while the request sits in accepted.
Nothing is marked paid from the payment action's success branch. The generated webhook flow parses the callback, finds the payment record by transaction id, and flips its status in a filtered update — if that update touches zero rows, this callback has already been handled and the flow stops. Fulfillment runs only when the payment was found, succeeded, and wasn't already processed.
Even then it verifies before it writes. It looks the order up from the callback, finds the payment that actually succeeded against it, and compares the amount and currency really paid against what the order says they should be. On a mismatch it logs and does nothing. On a match it runs two filtered mutations: order to paid where still pending, request to paid where still accepted.
Those where clauses are the entire idempotency story. A duplicate webhook — and Stripe will send one — finds nothing left in pending and changes nothing. Trusting a callback's amount instead of re-reading the order row is one of the quiet ways money walks out the door, and it costs one query to avoid. There is no refund flow; the prompt said not to build one.
Very little of what was typed into the Copilot described an interface. Almost all of the load-bearing instruction was invariants.
The five legal status values, and which transition each action is allowed to make. That coordinates are plain numbers, not a geo-point type. That roles come from the permission system rather than a column on the account table. That the photo must reach the model as an image. That distance is calculated in exactly one place. That the webhook is the only thing allowed to mark something paid, and that the amount is re-verified against the order before it does.
None of that is design, and all of it is the part that goes wrong when left implicit. The Copilot was perfectly capable of inventing the screens, the pastel palette and the homepage copy on its own — a marketing page, separate sign-ins for customers and staff, the request form, the quote and booking view, the filterable staff dashboard. What it needed from a human was the state machine and the rules about money, which is the actual division of labour behind building this way.
The same app exists built the other way — the Momen plugin driving Claude Code against a Momen backend, with a separate React frontend deployed on its own. That build is written up as multimodal AI quotes and live routing with no backend code.
The backends come out nearly the same shape. What differs is what you hold at the end. Copilot gives you one project — backend and frontend in the same editor, hosted by Momen, changed by chatting. The plugin gives you a Momen backend plus a codebase you own and host, which is what you want when the interface has to be something a page builder wouldn't naturally produce. The same split applies whether you drive it with Claude Code, Cursor or Codex.
Choose on frontend ambition, not backend complexity. The backend work is identical either way.
Generating all of this took 30 minutes to an hour, most of it spent writing the prompt rather than waiting on it. The real cost is upstream: settling the five status values and who may move between them took longer than building the app, which is the correct ratio.
The minimum plan is Pro at $99/month. Basic covers unlimited AI agents, Actionflows and third-party API integrations, but Stripe payments are Pro-only, and this build takes money. A version that stops at the staff decision and never charges anybody runs on Basic.
Three add-ons then grow with usage: AI Points, spent each time the agent reads a photo; object storage, since every request keeps its photo indefinitely; and outbound transfer, since those photos get served back to the staff dashboard. None of them track how many accounts exist — they track how many requests come in.
Sized around 4,500 cumulative customers and 225 operating days of accumulated data, the pricing calculator puts the add-ons at roughly $84/month, about $70 of that AI Points. That scenario was built for the version without payments, so borrow its add-on figures rather than its plan line. Early on, at a fraction of the request volume, the add-ons are a fraction of the cost.
Nominatim and OSRM are free public services, though OSRM's demo server isn't meant for production traffic. Stripe charges its usual per-transaction fee.
Try the live app · Clone it in the Momen editor
Cloning gives you the tables, roles, agent, Actionflows and pages exactly as described; the Stripe keys are yours to add. Submit a request with a photo to watch the quote land, then book against a real Bay Area address to see the routing run for real.