All work / AI engineering

GrantBot.

A multi-tenant grant research portal for small and mid-size nonprofits. Scans nine federal, state, and Philadelphia-foundation sources, scores each opportunity against the org’s mission, geography, populations served and budget band on a 0–100 scale, and one-click generates ready-to-send application and outreach drafts — piloted with Empowering Cuts, a Kensington-based mobile barbershop outreach.

Client
Pilot: Empowering Cuts Philadelphia (template for any 501(c)(3))
Role
Architect + solo engineer (product, scraping, scoring, portal UI)
Year
2026
Status
Portal live in pilot — 163 grants surfaced, $14.3M potential
GrantBot landing page — “Stop searching. Start winning grants.” with a live scanner terminal showing federal and Philadelphia sources being fetched and scored
01 · Context

“We know there’s money out there. We just don’t have a grant writer.”

Small Philadelphia nonprofits sit between two bad options: pay a grant writer $3–5K/month they can’t afford, or have the ED moonlight as the grant writer at 11pm after a full day of program work. Both lead to the same outcome — the same five obvious foundations get applied to over and over, and 90% of the available funding pool never gets touched.

The pilot client — Empowering Cuts — runs a mobile barbershop in Kensington serving unhoused individuals. Founder is a working barber. There is no grants person. The whole organization is one person and a van.

02 · Problem

Three jobs that look like one.

  • Discovery — nine separate databases (Grants.gov, SAMHSA/HHS, PA State, Philadelphia city, William Penn Foundation, Lenfest, Pew, foundation aggregators, Reddit nonprofit threads). Each has its own format, auth, rate limits, deduplication quirks.
  • Relevance — surfacing 3,000+ raw grants is worse than surfacing zero. The output has to be ranked against this specific org’s mission, geography, populations served, budget band, and feasibility.
  • Action — a ranked list is still homework. The thing that actually moves money is a draft application or outreach email the ED can edit and send tonight.
03 · What I built

Tools do the work, agent does the reasoning, portal makes it self-serve.

Built on a WAT (Workflows / Agents / Tools) architecture — deterministic Python tools scrape, normalize, and write data; the agent layer handles ranking and draft generation; everything fronts a multi-tenant FastAPI portal so a nonprofit can sign in, fill out a 3-minute intake, kick off a scan, and have ranked results before their coffee finishes.

Architecture

Grants.gov SAMHSA/HHS PA State Philly Foundations William Penn Reddit │ │ │ │ │ │ │ └────────────┴───────────┴─────────┴───────────┴──────────────┴───────────┘ │ ▼ fetch_*.py tools (rate-limited, paginated) │ ▼ deduplicate_grants.py (fuzzy match) │ ▼ score_grants.py → Anthropic Claude │ ▼ SQLite + Alembic ← FastAPI (magic-link auth, sessions) │ ▼ /intake/ /grants/ /account/ /api/v1/* (onboarding) (results UI) (org + plan) (REST endpoints) │ ▼ generate_application_draft.py + generate_email_drafts.py

Stack

FastAPI + Uvicorn SQLAlchemy + Alembic + SQLite Anthropic Claude (scoring + drafts) BeautifulSoup + requests (scrapers) Magic-link auth (HMAC sessions) 9 grant data sources Multi-tenant orgs + tiered plans fpdf2 (PDF exports) PRAW (Reddit signal)

Key decisions

  • WAT split from day one. Tools are dumb and deterministic (Python scripts, testable, cheap to re-run). The agent only does the parts that need judgment — scoring and draft writing. Keeps API spend bounded and makes failures debuggable.
  • Five-factor weighted scoring, not one black-box number: Mission Fit 35%, Geographic Fit 20%, Feasibility 20%, Deadline Urgency 15%, Amount Fit 10%. The breakdown is shown in the UI so the ED can argue with it.
  • Multi-tenant from v1, not added later. Every record is keyed on org_id; tiered plan limits enforced server-side. EmpoweringCuts is the first tenant, not the only tenant the schema can hold.
  • Magic-link auth instead of passwords. Nonprofit EDs don’t need another password to lose. Same dev/prod path — dev exposes the link in the API response, prod sends it via email.
  • Embedded demo fallback on the results page — if the API call fails (e.g. opened from file:// for a demo), the page hydrates from a static DEMO array. Means I can show the product without a live backend.
  • Drafts gated by tier. Free tier sees the top results and the scoring; paid tiers get one-click application drafts and outreach emails. The paywall is a real product line, not a placeholder.
04 · Selects

One portal, four surfaces.

Each surface handles one job in the funnel: convince, onboard, deliver, manage. Same design system across all four; everything is real data from the EmpoweringCuts pilot.

Intake form — Tell us about your organization, with $8M+ potential funding stat
Intake · 3-minute org profile
Grant Opportunities results viewer — 163 results, 49 high priority, $14.3M potential funding
Results · 163 grants, ranked & filterable
Expanded grant card showing description, eligibility, and Mission/Geographic/Feasibility/Deadline/Amount score breakdown
Scoring · 5-factor weighted breakdown
Organization Profile settings — mission, populations served, focus areas, budget range
Account · org profile + plan
05 · Outcome

Pilot live. Real grants. Real money on the table.

Numbers below are from the live scan run for Empowering Cuts (March 2026).

163
Grants surfaced & scored in one scan
49
High-priority (score ≥ 70) opportunities
$14.3M
Total potential funding in the matched pool
9
Federal, state, and Philadelphia foundation sources
3 min
From intake form to ranked results
1-click
Generates a tailored application draft

Before GrantBot, EmpoweringCuts had applied to two foundations they already knew about. The first scan returned a ranked list of 49 high-priority matches across DBHIDS, Lenfest, Citizens Bank Foundation, Pew, Independence Foundation, and Wells Fargo — most of which the founder had never heard of. The thing that’s actually scarce isn’t the funding; it’s the attention to find and apply for it.

06 · Lessons

What I’d do the same. What I’d do differently.

Keep: WAT split. Letting Python tools own scraping/dedup/IO and reserving the LLM for ranking + drafting kept the AI bill in dollars, not hundreds of dollars, per scan.

Keep: Multi-tenant from v1. Building org-scoping into every query on day one cost half a day; retrofitting it later would have been a rewrite.

Keep: Show the score breakdown, not just the number. Operators trust a 5-factor weighted score they can argue with more than a single 0–100 they can’t.

Change: First version of the scrapers each lived in its own script with copy-pasted rate-limit and pagination logic. Next pass extracts a shared base scraper class — adding the 10th data source should be 50 lines, not 250.

Change: Started with a generic intake form; the real value-unlock was budget band + service-area. Future onboarding pushes those two questions to the top and treats the rest as progressive enrichment.