What I Learned Building a Civic Platform on Government APIs
I like government APIs for the same reason I like old catalogs and provenance records: they reveal how an institution thinks when nobody is trying to flatter you.
Congress.gov, OpenStates, and the Census Bureau each have different ideas about what a congressional district is, how to identify it, and whether you’re allowed to ask. Trying to make those sources agree taught me more about systems integration than a lot of cleaner enterprise work ever did.
The problem
ConcernedFriends is a civic intelligence platform. Enter your address. See your federal and state representatives. See your district’s demographics. Eventually, see how federal money flows to your area.
The user interaction is simple. The data aggregation behind it is not.
Six APIs, six worldviews
Nominatim (OpenStreetMap) geocodes addresses into latitude/longitude coordinates. It’s free, rate-limited, and generally reliable. This is the easy one.
Census Geocoder takes an address and returns congressional districts, state legislative districts, and FIPS codes. It’s the authoritative source for “what district is this address in.” The API is functional but its documentation assumes you already understand Census geography terminology.
Congress.gov provides federal representative data — members of Congress, their committees, voting records. The API requires a key and returns data structured around congressional sessions and chambers. Finding “who represents this state” requires understanding how the API models the relationship between states, districts, and members.
OpenStates provides state legislator data by geographic coordinates. You send lat/lng, it returns state-level representatives. The v3 API is well-designed but covers a different scope than Congress.gov — state legislators, not federal ones. The two APIs have no shared identifier format.
Census ACS (American Community Survey) provides demographic data — income, education, housing, population — by geographic unit. The ACS 5-year estimates are the most reliable for small geographies. The variable naming system is a code that requires a separate lookup table to decode (B19013_001E is median household income).
USAspending (planned for Phase 2) provides federal spending data by geography. Another API, another data model, another set of identifiers.
What makes this hard
Identity disagreement
Each API identifies geographic units differently. The Census Geocoder returns FIPS codes. Congress.gov uses state abbreviations and district numbers. OpenStates uses its own jurisdiction identifiers. The Census ACS uses FIPS codes but at different geographic levels than the Geocoder returns.
There’s no universal “district ID” that works across all six APIs. The backend has to maintain a mapping layer that translates between identifier formats.
Temporal disagreement
Congressional districts change after redistricting. The Census Geocoder may return different district assignments than Congress.gov expects, depending on which redistricting cycle each is using. State legislative boundaries change on a different schedule.
Building a system that presents current data means understanding which “current” each API is using, and handling the cases where they disagree.
Rate limits and reliability
Government APIs have varying reliability. Some enforce rate limits. Some have intermittent downtime. Some return errors in formats that don’t match their documentation.
The Phase 1 architecture makes live API calls per request. This works for individual lookups but can’t scale to a national dashboard. Phase 2 pre-computes everything: nightly ETL jobs fetch all 435 congressional districts, sync all representatives, import Census demographics, and store everything in PostgreSQL with PostGIS for instant spatial queries.
The performance target: address to full dashboard in under 500ms.
Schema disagreement
What does a “representative” look like? Congress.gov returns federal members with party, state, district, committee assignments. OpenStates returns state legislators with party, district, chamber, and a different set of metadata. The frontend needs a unified representation.
The Go backend handles this normalization. Each API gets its own service module that fetches and transforms data into a shared model. The handler layer merges federal and state results into a single representative list.
Architecture decisions
Go for the backend. High-throughput API aggregation with concurrent requests is Go’s strength. The standard library’s net/http and goroutines handle parallel API calls cleanly. No framework — just Chi for routing and stdlib for everything else.
SvelteKit for the frontend. The dashboard is data-dense: representatives, demographics, charts. SvelteKit’s reactive data binding and Svelte 5’s runes make complex data display straightforward. LayerCake and D3 handle visualization.
PostgreSQL + PostGIS for Phase 2. Spatial queries (“what district contains this point?”) are PostGIS’s core competency. Materialized views handle pre-computed national rankings. Redis caches hot representative and demographic data.
Phased scaling. Phase 1 is on-demand API calls — functional but slow. Phase 2 is pre-computed data — fast but requires ETL infrastructure. Phase 3 is a national view with choropleth maps and trend analysis. Each phase builds on the previous one without rewriting it.
What government APIs teach you
Working with government data teaches specific lessons:
Documentation reflects institutional priorities, not user needs. The Census ACS documentation is exhaustive about methodology and statistically precise about margins of error. It is minimal about how to actually use the API to get the number you want. The information is there; the path to it assumes expertise the average developer doesn’t have.
Data models reflect organizational structure. Congress.gov models data around legislative sessions because that’s how Congress organizes itself. OpenStates models data around jurisdictions because that’s how state governments organize themselves. Neither is wrong. Both make integration harder because they optimize for their own consumers, not for interoperability.
Reliability is uneven. Some government APIs are well-maintained. Others appear to be the output of a compliance checkbox. Rate limits are sometimes documented, sometimes discovered experimentally. Error responses vary from structured JSON to plain text to no response at all.
The hard problem is agreement, not access. Getting data from any single API is straightforward. Getting six APIs to agree on what “your district” means, who represents it, and what the demographics look like — that’s the integration problem. And it’s the same class of problem that shows up in every systems integration project: different sources model the same reality differently, and someone has to reconcile them.
What’s next
ConcernedFriends is early. The on-demand lookup works. The pre-computed national dashboard is planned but not built. The USAspending integration is Phase 2.
The platform needs production maturity before it carries the weight of civic responsibility. But the architecture is designed for it: phased scaling, pre-computed data, spatial queries, and a clear separation between API aggregation (Go backend) and data presentation (SvelteKit frontend).
The name comes from Martin Luther King Jr.’s letter from Birmingham Jail, addressed to people of goodwill who expressed concern but stopped short of action. The platform exists to make the gap between concern and action smaller — by making civic information accessible enough to act on.