Enrich a CSV of addresses with Census demographics (Python)
Turn a plain list of US addresses into an enriched table with rooftop coordinates, the 15-digit Census block, and official Census demographics — in about 40 lines of Python, using the Weganar / geocodercloud API.
This is the exact workflow behind demographic enrichment, HMDA/fair-lending prep, and market analysis: geocode each address, get its Census geography, then pull the numbers for that geography.
What you'll need
-
A free account at weganar.com and an API key (create one on your dashboard). New accounts get 5,000 geocodes/month plus 150 free credits on first sign-in.
-
Python 3.9+ and
requests(pip install requests). -
A CSV with an
addresscolumn, e.g.:address "1600 Pennsylvania Ave NW, Washington, DC 20500" "782 N Lakewood Ave, Ocoee, FL 34761"
The two calls
Both endpoints are POST /api/v1/..., take {"address": "..."}, and
authenticate with an X-API-Key: gck_... header.
1. Geocode — resolve the address to a point and its Census block:
curl -X POST "https://weganar.com/api/v1/geocode" \
-H "X-API-Key: gck_your_api_key" \
-H "Content-Type: application/json" \
-d '{"address": "1600 Pennsylvania Ave NW, Washington, DC 20500"}'
{
"matchCode": "NAD_PARCEL",
"locationCode": "ROOFTOP",
"lat": 38.897675,
"lon": -77.036547,
"fips": "11001",
"censusBlock": "110010062021031"
}
That censusBlock is a 2020 GEOID: state (2) + county (3) + tract (6) + block
(4). Slice it for tract or block group.
2. Demographics — pull Census/ACS data for that address (20 credits per successful report; nothing is charged when data isn't available):
curl -X POST "https://weganar.com/api/v1/demographics" \
-H "X-API-Key: gck_your_api_key" \
-H "Content-Type: application/json" \
-d '{"address": "1600 Pennsylvania Ave NW, Washington, DC 20500"}'
The script
A ready-to-run version lives at
examples/enrich_csv_demographics.py.
The core is just:
import requests
BASE = "https://weganar.com"
HEADERS = {"X-API-Key": "gck_your_api_key", "Content-Type": "application/json"}
def enrich(address):
geo = requests.post(f"{BASE}/api/v1/geocode",
headers=HEADERS, json={"address": address}).json()
row = {"lat": geo.get("lat"), "lon": geo.get("lon"),
"census_block": geo.get("censusBlock"), "fips": geo.get("fips")}
# Only spend credits on demographics if the address actually matched.
if geo.get("matchCode") not in (None, "NO_MATCH"):
demo = requests.post(f"{BASE}/api/v1/demographics",
headers=HEADERS, json={"address": address}).json()
if demo.get("available"):
row["population"] = demo.get("population")
row["median_household_income"] = demo.get("medianHouseholdIncome")
return row
Run it over a file:
export WEGANAR_API_KEY=gck_your_api_key
python examples/enrich_csv_demographics.py addresses.csv --out enriched.csv
Notes & good habits
- A no-match is an HTTP 200, not an error — check
matchCode == "NO_MATCH"(with anoMatchReason) rather than relying on the status code. - Guard your credits. The script only calls
/demographicswhen the geocode matched, so you never spend credits on an address that didn't resolve. - You own the output. Unlike Google Maps, there's no restriction on storing the coordinates or GEOIDs you get back — write them to your warehouse and keep them.
- Batching. Call per address at your tier's throughput; add
--sleepif you want to pace requests. Higher volumes are available on the Growth/Enterprise plans.
Where to go next
- Swap
/demographicsfor/demographic-trend,/schools,/hazards,/crime, or/property-intelto enrich with other datasets on the same address. - See per-state rooftop coverage at
/coverage/states.