# Build spec: territory runner A tool that turns "every roofer in Texas" into a plan, runs it within a budget the user sets, and reports honestly on what was and was not covered. Build this when the user needs **market coverage**, not a sample. **Before you build:** ask. See https://lyreleads.com/build for the four rules that apply to every build on this API. --- ## The problem it solves A single Google Maps search returns far fewer businesses than exist. Measured on one metro category in July 2026: one search reached under 4% of at least 1,605 findable businesses. Eight deep-paginated searches reached 69%. Coverage does not come from more pages. It comes from **more searches**: more keyword variants, more places, smaller places. Doing that by hand is tedious and easy to do badly, which is exactly why it should be a program. --- ## What it does 1. User states a target: a business type, a region, a token budget. 2. The tool expands that into a **plan**: a grid of (keyword × place) searches. 3. Shows the plan and the worst-case cost **before** anything runs. 4. Executes it, resumably, respecting the budget. 5. Reports coverage honestly, including what it did not reach. --- ## Plan expansion This is the part with the actual intelligence in it, and it is where you earn your keep. ### Keyword variants Google matches against the business's own listed category, and businesses in one trade do not all pick the same category. One word is one slice. Generate 4–8 variants per trade. For roofing: `roofer`, `roofing contractor`, `roofing company`, `roof repair`, `roof replacement`, `commercial roofing`. Include the generic and the specific, and the noun and the agent form. You are a language model. Generating these is the easiest thing you will do all day, and it roughly multiplies coverage. Do not skip it because one word "seems fine". Show the user the list and let them edit it. They know their trade better than you do. ### Places Bigger places are not better. A search is centred on a point, so one search on a large metro misses its edges entirely. Ten searches on ten suburbs beat one on the metro. Expand a region into its cities and large towns. For a metro, expand into named districts and adjacent municipalities. Let the user paste their own list, because they often have one. ### The grid `keywords × places` searches. Warn the user if the grid is enormous before you show a cost, because the number tends to surprise people. --- ## Budget, shown before anything runs The one screen that must be right. ``` Plan: 6 keywords × 24 places = 144 searches Pages per search: 2 Worst case: 144 × 40 = 5,760 tokens Your balance: 5,000 tokens Duplicates are free, and overlap between keyword variants is heavy, so the real cost is usually well below worst case. Actual spend is reported as it runs. ``` Never start a run that could exceed the balance without saying so. Let them cap it: "stop at 2,000 tokens" is a reasonable and common request. `GET /api/v1/balance` for the current balance. It is free. --- ## Execution ``` POST /api/v1/search { query, city, country, pages } ``` Sequential, not parallel. This is a shared rate limit and a token budget; hammering it gets you `429`s and helps nobody. Between calls, track cumulative `tokens_used` from each response. Stop when the budget is hit and say so clearly. **Make it resumable.** Persist the plan with a status per cell (`pending`, `done`, `failed`, `skipped`) before you start. A 144-search run will be interrupted — a laptop sleeps, a network drops. Resuming should be obvious and safe, and re-running a completed cell should be free anyway because duplicates are not charged. Show live progress: which cell, how many businesses so far, tokens spent, tokens left. --- ## Coverage reporting Be honest here. This is the difference between a useful tool and a false sense of completeness. After a run, report: - **Total unique businesses found**, and how many were new versus already held. - **Per-place counts.** A place that returned 3 when its neighbours returned 60 was probably not covered; it may need its own sub-searches or a different keyword. - **Per-keyword yield**, including uniques contributed. A variant that only returned businesses other variants already found can be dropped next time. One that contributed 40 uniques should be kept. - **What you cannot know.** You do not know the true total. Say so. The right claim is "144 searches surfaced 1,610 unique businesses, and additional variants were still contributing new ones when the budget ran out", never "we found all the roofers in Texas". A good signal for the user: if the last few searches were still returning new businesses, coverage is not complete. If they were returning almost entirely duplicates, you are close to what Maps will give you. --- ## After the run Coverage is step one. The businesses are saved but only partly enriched. - `POST /api/enrich` with the new ids, 1 token per 5 leads. - `POST /api/v1/count` (free) to show what the territory actually contains before spending more. - `POST /api/enrich/find-decision-makers` on the segment worth contacting, not on all of it. Show the count breakdown before each of these. The user has just spent a lot of tokens on discovery and should decide deliberately about the next spend. --- ## Traps specific to this build **Running the grid in parallel.** Rate limits, and no faster in practice. **Not persisting the plan before starting.** An interrupted run with no state is a lost run. **Treating overlap as waste.** Heavy overlap between keyword variants is expected and free. It is evidence the coverage is converging, not evidence of a bug. **Claiming completeness.** You cannot know the denominator. Never imply you do. **Expanding a region into places with no listings.** Filter obviously-empty results out of the report rather than presenting a wall of zeros; but keep them in the plan record so a rerun does not retry them. **Ignoring `429`.** Exponential backoff, resume where you stopped.