Who Reps Me?
Every official who represents you, one address at a time.
In April 2025, Google shut down the Civic Information API. It was the only free nationwide way to look up a city council member or school board rep, and when it went away nobody replaced it. That bothered me enough to spend a few weekends rebuilding the lookup myself, federal down to city hall, on infrastructure that costs me nothing to keep running.
Data Pipeline
83 jurisdictions
GitHub Actions
The Problem
Google walked away.
The paid vendors didn't blink.
For over a decade, Google's Civic Information API was the quiet backbone behind most "find your representative" tools on the web — free, nationwide, and the only source that reached down to city council and school board level. In April 2025, Google shut it down.
Every provider left standing charges for it. Cicero and BallotReady both sell commercial civic data licenses — reasonable products, but not something a free public lookup tool can build on. Federal data is still easy (Congress publishes it). State data is workable (most legislatures publish open data). Local data — the level that actually governs zoning, trash pickup, and school boards — had no free nationwide source left at all.
"I went looking for a free replacement and kept finding the same thing: a tool that covered federal and state fine, then asked for money at exactly the level I cared about. Most of them had been quietly running on Google's API the whole time."
How It Works
Type an address.
Get everyone who represents you.
One search box, three levels of government. Federal senators and representatives, state legislators and executives, and the city council members and school board reps most "find your rep" tools don't even attempt to cover.
Live demo — search by address, then browse results grouped by LOCAL / STATE / FEDERAL.
Data Sources
Four sources.
Zero paid APIs.
No single free source covers all three levels of government, so the pipeline stitches together four of them — one per layer, plus geocoding to route the address to the right district.
US House & Senate
Via 5calls' free public API — no key required, generous limits, built for exactly this use case.
Legislators & Executives
Via Open States v3 — state house, state senate, governor, and other statewide offices.
City & County Officials
No API exists for this layer, so I built a scraper that reads municipal websites and uses an LLM to pull structured official records out of whatever it finds. This is the part that took the longest by a wide margin.
Address → Coordinates
US Census Geocoder first, Nominatim (OpenStreetMap) as fallback whenever Census can't resolve the query.
Architecture
Search data flow.
One address in, three lookups run in parallel, one merged response rendered by level of government. Geocoding is the fork in the road — get it wrong and everything downstream degrades.
Field Notes
The bare-ZIP bug nobody warns you about.
The Census Geocoder's onelineaddress
endpoint is an address-range matcher, not a general-purpose geocoder — it needs an actual
street number and street name to match against. Feed it a bare city name or a ZIP code alone and
it returns zero matches, even though the search box invites "address or zip code." Without a
fallback, that silently degrades a bare-city/zip search down to federal-only results: two senators
and a representative, no error message explaining why the state legislator and city council
never showed up.
Detect the shape of the query
Before ever calling the Census Geocoder, the query is classified: a full street address, a bare 5-digit ZIP, or a "City, State" pair. Only a full address goes straight to the address-range matcher.
Bare ZIP → zippopotam.us
A 5-digit ZIP with nothing else routes to zippopotam.us, a free lookup that returns a ZIP's centroid lat/lng directly — no street address required, no matching against a range.
Bare "City, State" → Nominatim, then Census coordinates
A city-only query resolves through Nominatim (OpenStreetMap's free geocoder) to a lat/lng, which is then passed to the Census Bureau's coordinates endpoint — a different endpoint than onelineaddress, one built to accept raw coordinates instead of a street-range match — to recover the district and jurisdiction data the address endpoint would have provided.
Autocomplete nudges users toward a full street address before they ever hit the bare-ZIP edge case.
Field Notes
Municipal websites are not data.
The local layer is the whole reason this project exists, and it's the part that fought me hardest. There is no schema. One city publishes its council on a clean roster page. The next one puts it in a PDF of last year's meeting minutes. The next one has a photo of the council with the names in the caption. A few still list a member who lost an election two cycles ago.
My first pass was ordinary scraping — find the table, find the list, parse the names. It worked on maybe a third of the jurisdictions I tried and broke on the rest, and every fix I made for one city broke the assumption I'd made for another. Writing a parser per city was never going to finish.
What actually worked was inverting it: stop trying to parse the page, and hand the page to an LLM with a strict output schema instead. The model is good at the thing that was defeating me — reading an unfamiliar layout and understanding that "Place 4" and "District 4" and "Ward 4" all mean the same thing. That moved the hard problem from parsing to verification, which is a much better problem to have, because a wrong answer at least looks like an answer I can check.
It also meant I couldn't trust the output. An LLM reading a stale page will confidently give you a stale council member. So nothing goes live from the scraper alone — every shard gets reviewed before it's committed, and the crowdsourced correction path below exists because I already knew the data would be wrong in places I'd never notice on my own.
Honest caveat: 24 states, not 50, is exactly this problem. Local coverage expands at the speed I can review it, not the speed the scraper can run.
Crowdsourcing
The database that fixes itself.
Local officials data goes stale fast — elections, resignations, redistricting, a city council seat that flips mid-term. Instead of an admin dashboard nobody but me would ever open, corrections route through GitHub, where they're triaged automatically before a human ever has to look.
user-reported issue?"] D -->|"Yes"| E["Comment on existing GitHub issue"] D -->|"No"| F["Open new GitHub issue"] E --> G["Appended to tracking file"] F --> G G --> H["Human review"]
The crowdsource submit modal — a link and a note is all it takes.
Infrastructure Philosophy
A JSON file is the database.
There's no database server and no always-on worker. A per-state JSON shard committed to the git repo is the database — versioned, diffable in a pull request, free to host. A GitHub Actions cron job does the work a background service normally would, waking on a schedule to re-scrape and re-validate one shard at a time. Netlify serves the results from the edge with no origin compute behind them. The data is small enough and changes slowly enough that a real database would have been a bill I took on for nothing.
"The constraint drove the architecture, not the other way around. This is a side project nobody pays for, so anything with a monthly bill was off the table from the start — which turned out to be a useful forcing function."
Coverage
24 states and counting.
Federal and state coverage is nationwide from day one — Open States and 5calls both cover all 50 states. Local coverage is what's still expanding: each state's local-officials shard has to be built, scraped, and verified one jurisdiction at a time.
24 of 51 states and territories currently have live coverage: Washington, Oregon, California, Nevada, Utah, Arizona, Colorado, Oklahoma, Texas, Illinois, Tennessee, Michigan, Indiana, Virginia, North Carolina, Ohio, Pennsylvania, Maryland, Georgia, New York, New Jersey, Florida, Massachusetts, Hawaii.
Screens
What it looks like.
How I Built This
I didn't write most of this code.
Worth saying plainly, because it's the honest version and because how I work is part of what I'm showing here. I'm a product designer, not a software engineer. Nearly all of the code in this project was written by AI tooling — Claude and Cursor — working from my direction.
What I did was everything around that. I decided what the product was and who it was for. I chose the four data sources and worked out which layer each one could actually cover. I made the call that a JSON file in git would stand in for a database, and that corrections should route through GitHub instead of an admin panel. I designed every screen. And when the results came back wrong — a bare ZIP silently returning federal-only, a scraper confidently inventing a council member — I'm the one who noticed, worked out why, and decided what the fix should be.
I'd rather tell you that up front than have it come out in an interview. If what you need is someone who can write a red-black tree on a whiteboard, that isn't me. If what you need is someone who can take a vague problem, decide what should exist, and drive it all the way to a deployed thing real people can use, that's the entire point of this page.
"The model handles syntax. Deciding what to build, noticing when it's wrong, and knowing what wrong even looks like — that part didn't get automated."
Outcome
What it produced.
450 officials indexed
Federal, state, and local officials, unified into one searchable dataset.
24 states covered
Live local-officials coverage, with federal and state coverage nationwide.
83 jurisdictions
Cities and counties with a scraped, human-reviewed local officials shard.
$0/month recurring cost
No database bill, no server bill, no API bill — every dependency runs on a free tier.
Tech Stack