+ How this was built +
++ This is a Bay Area apartment finder built on Parallel's FindAll and Task APIs. You describe a place + in plain language and get back individual listings you can actually open, each checked against your + criteria. The whole thing is one Next.js app on Vercel. There is no backend server and no database. +
+ +One FindAll call does the hard part
++ A search does not send a keyword query. It sends an objective plus a few match conditions, and FindAll + discovers candidates across the web and checks each one. The parts that matter: + {" "}match conditions decide whether a candidate is kept (booleans like + "is this one real listing?" and "is it under budget?"), and{" "} + enrichments are the 18 structured fields pulled back for each match + (rent, beds, address, and so on). Enrichments run on the Task API, one task per match. +
+{FINDALL_BODY}
+ + Bedroom count is intentionally not a match condition. Strict conditions cause zero-match runs when the + API cannot verify them from page text, so beds come back as an enrichment and get filtered in code. +
+ +The app is a stateless client driving the run
+
+ FindAll runs are asynchronous, so the browser drives each search through short serverless calls. The
+ server holds no state between them. The only thing that persists is the user's saved shortlist, kept
+ in their own browser via localStorage.
+
{PIPELINE}
+
+ Verified does not mean openable
++ This was the part that took the most work. FindAll verifying a candidate means the page matched the + conditions. It does not guarantee a link a person can click and rent from. The gap between those two is + most of the application: +
+
+ Category and search pages match "describes rentals" but you cannot rent them, so the match condition
+ rejects index pages and a URL guard filters search or category paths. A few aggregators
+ ({" "}zillow.com, yelp.com, loopnet.com, crexi.com)
+ bot-wall the real listing, so they are blocked rather than sending someone to a dead link. And when a
+ candidate carries several URLs, the app picks the most specific individual one instead of a browse page.
+
Discovery is variable, so plan for thin runs
++ A thin query sometimes comes back mostly category pages and finalizes near-empty, while the same query a + minute later returns six. Two guards handle it. If discovery never fills its match limit (a rare or + over-constrained query), the app proceeds to enrichment once it has run long enough with at least one + match, rather than waiting forever. And if a run that actually completed still finalizes with almost + nothing, it runs one more fresh pass and keeps whichever found more. +
+
+ Discovery plus per-listing enrichment is inherently a multi-minute operation, so the UI shows a timer and
+ streams results in as they verify. generator: base is the right tier for a broad
+ city-wide query; core and pro search harder for rarer, more specific ones.
+
A second, targeted check with the Task API
++ For listings from untrusted sources, a fraud check runs on the Task API directly (the same API that + powers the enrichments). The schema asks for concrete signals rather than a vague "is this a scam" + score, and the signals are weighted in code. +
+{TASK_BODY}
+
+ Scoring
++ Each listing gets a 0 to 100 score from price fit and proximity to a reference point (with a bedroom-fit + penalty used only for ranking). Because results are fetched fresh on every search, staleness is handled + separately: listings the API reports inactive, or past a freshness window, are flagged and hidden by + default. +
+ +Live configuration
+Everything is env-driven. These are the values this instance is running with right now:
+