US Census Data
Plaza carries US Census Bureau boundaries and statistics as Plaza-curated datasets. Each boundary feature has its demographic and economic attributes on it, so “which tract is this point in, and what is its median income?” is one query. You query them like any other dataset: dataset("census-tracts") in PlazaQL, or datasets in the body of POST /api/v1/features.
The data is a U.S. Government work in the public domain. It is free to query, cache and redistribute, and it is compatible with the ODbL terms on the OpenStreetMap data you may combine it with. The attribution text is “U.S. Census Bureau”. See Attribution.
What’s included
Section titled “What’s included”There are 36 datasets in four groups.
Boundaries from TIGER/Line
Section titled “Boundaries from TIGER/Line”These are the precise legal and statistical boundaries. Use them for point-in-polygon lookups and for apportioning statistics to an area you draw.
| Slug | Geography | Notes |
|---|---|---|
census-states | States and equivalents | 56 features |
census-counties | Counties and equivalents | 3,235 features |
census-county-subdivisions | County subdivisions | Towns, townships, districts |
census-tracts | Census tracts | About 85,500 features |
census-block-groups | Block groups | About 242,000 features |
census-places | Incorporated places and census designated places | |
census-zctas | ZIP Code Tabulation Areas | Statistical areas, not postal routes |
census-cbsas | Metropolitan and micropolitan areas | |
census-congressional-districts | Congressional districts | |
census-state-legislative-upper | State senate districts | |
census-state-legislative-lower | State house districts | |
census-school-districts-unified | Unified school districts | |
census-school-districts-elementary | Elementary school districts | Not every state has them |
census-school-districts-secondary | Secondary school districts | Not every state has them |
census-urban-areas | Urban areas |
Cartographic boundaries for display
Section titled “Cartographic boundaries for display”Eight siblings of the layers above end in -cb: census-states-cb, census-counties-cb, census-tracts-cb, census-block-groups-cb, census-places-cb, census-zctas-cb, census-cbsas-cb and census-congressional-districts-cb. They are generalized to 1:500,000, so they draw cleanly on a map and are much lighter than the precise files. They carry the same attributes. Use the precise datasets when a point close to a boundary matters.
Place points
Section titled “Place points”census-gazetteer-places has every place as a point at its internal point, with land and water area. It’s a lightweight alternative to the place polygons when you only need a location.
ACS data profiles
Section titled “ACS data profiles”Twelve datasets hold the complete American Community Survey (ACS) 5-year data profiles, tables DP02 to DP05, for tracts, counties and places. Each is a point at the geography’s internal point. The slug pattern is acs5-2024-dp03-tract: dp02 to dp05, then tract, county or place. Property keys are the lowercased Census variable codes, such as dp03_0062e, and the field dictionary has the label for each. Use these when you need a variable that isn’t in the curated set described below.
Vintages and refresh
Section titled “Vintages and refresh”Each dataset records its vintage. Boundaries are the newest TIGER/Line release the Census Bureau has published. Attributes come from the ACS 2024 5-year release, the 2020 Census redistricting file (PL 94-171) and the newest Population Estimates.
After the first load, once auto-sync is turned on, every dataset checks for new data at 03:00 UTC on the first of each month. Until then a dataset is refreshed only when Plaza reloads it by hand. The check is cheap. Plaza reloads a dataset only when a boundary file or an attribute release has changed, then switches to the new copy in one step, so a query never sees a half-loaded dataset. Feature IDs stay stable across refreshes. Datasets explains vintage, refreshed_at and the sync history endpoint.
A dataset that hasn’t finished its first load is pending. GET /api/v1/datasets leaves pending datasets out unless you pass include_pending=true. Naming one in a query is an error until the load finishes:
dataset not yet available: census-tracts (status: pending)The "plaza" and "all" keywords skip datasets that aren’t ready. Before you depend on a layer, check its status:
curl -H "x-api-key: $PLAZA_API_KEY" \ "https://plaza.fyi/api/v1/datasets?scope=plaza&include_pending=true"Features and GEOIDs
Section titled “Features and GEOIDs”Every feature’s ID is its GEOID, the Census Bureau’s code for the area. The geoid property holds the same value. GEOIDs nest, so a longer one starts with the shorter ones above it:
| Geography | Length | Example | Built from |
|---|---|---|---|
| State | 2 | 36 | State FIPS code |
| County | 5 | 36061 | State + county |
| County subdivision | 10 | County + subdivision | |
| Tract | 11 | 36061010200 | County + tract |
| Block group | 12 | 360610102001 | Tract + block group |
| Place | 7 | 3651000 | State + place |
| ZCTA | 5 | 10118 | The ZIP Code Tabulation Area itself |
| CBSA | 5 | 35620 | The core-based area code |
| Congressional district | 4 | 3612 | State + district |
To get the county of a tract, take the first 5 characters of its GEOID. Always treat GEOIDs as strings. Leading zeros matter: Alabama’s state code is 01.
Boundary features carry geoid, name, aland and awater (land and water area in square meters), intpt_lat and intpt_lng (the internal point), vintage, and, for geographies that sit inside a state, state_fips and state_abbr. The rest are the statistics below.
Statistics
Section titled “Statistics”Every level from tract up has about 250 curated ACS properties with plain names such as population, median_household_income, pct_below_poverty and median_gross_rent. Names are stable. If the Census Bureau renumbers a variable between releases, Plaza follows the label and your property name keeps working.
A few things to know:
- Margins of error. Forty key estimates also have
<name>_moe, the 90% margin of error.median_household_income_moegoes withmedian_household_income. - Missing values. The Census Bureau’s “no estimate” codes become
null. Useis_number()in a filter if a null would break your expression. - 2020 counts. Properties ending in
_2020are counts from the 2020 Census, such aspopulation_2020andhousing_units_2020. They are on states, counties, county subdivisions, tracts, block groups and places. - Population estimates. States, counties, places and county subdivisions also have
popest_population,popest_birthsand related properties. - Block groups. The ACS doesn’t publish profile tables below the tract, so block groups carry detailed-table variables. They are named
population_detailedandmedian_household_income_detailed. Tracts have both forms.
Census Variables lists the properties by theme: population and age, race and ethnicity, households, income and poverty, employment and commuting, education, housing, internet access, health insurance, and nativity and language. For one dataset, GET /api/v1/datasets/census-tracts/dictionary returns the same list with types and descriptions.
Find the geography at a point
Section titled “Find the geography at a point”The most common question is “which census geography is this point in?” In PlazaQL, ask the layer you want:
// The tract at the Empire State Building, with its demographicssearch(dataset("census-tracts")) .contains(point(40.7484, -73.9856)) .first();Over REST, put the dataset in datasets and the point in contains. Without type, only dataset features come back:
curl -X POST https://plaza.fyi/api/v1/features \ -H "x-api-key: $PLAZA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "datasets": ["census-tracts"], "contains": { "type": "Point", "coordinates": [-73.9856, 40.7484] }, "limit": 1 }'To get every level at once, reverse geocode with include=census. See Reverse geocoding.
Filter on statistics
Section titled “Filter on statistics”Filter on any property with t["name"]. Census values are numbers, so wrap them in number() for comparisons:
// Tracts within a 15-minute walk with median household income above $75,000$walk = isochrone(center: point(40.7484, -73.9856), time: 900, mode: "foot");search(dataset("census-tracts")) .intersects($walk) .filter(number(t["median_household_income"]) > 75000);The isochrone makes this a premium request. The same filter over a fixed bbox or polygon is one standard request.
In the REST body, tags matches property values exactly. Numeric comparisons such as “above 75,000” need PlazaQL.
curl -X POST https://plaza.fyi/api/v1/features \ -H "x-api-key: $PLAZA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "datasets": ["census-counties"], "tags": { "state_abbr": "NY" }, "limit": 100 }'Aggregate
Section titled “Aggregate”Add up a statistic over any set of features. In PlazaQL, end the chain with .sum(), .min(), .max() or .avg():
// Total population of New York State's countiessearch(dataset("census-counties")) .filter(t["state_abbr"] == "NY") .sum(number(t["population"]));The REST twin is output plus expr, the name of the property to aggregate:
curl -X POST https://plaza.fyi/api/v1/features \ -H "x-api-key: $PLAZA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "datasets": ["census-counties"], "tags": { "state_abbr": "NY" }, "output": "sum", "expr": "population" }'{ "value": 19400000 }The number is illustrative. Scalar aggregates come back as {"value": n} from both /features and /query. With output=count, the answer is {"counts": {...}, "total": n}.
group_by splits the aggregate by a property, in both interfaces:
// Population by statesearch(dataset("census-counties")) .group_by(t["state_abbr"]) .sum(number(t["population"]));curl -X POST https://plaza.fyi/api/v1/features \ -H "x-api-key: $PLAZA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "datasets": ["census-counties"], "output": "sum", "expr": "population", "group_by": "state_abbr" }'{ "groups": { "NY": 19400000, "NJ": 9500000, "CT": 3700000 }, "total": 3 }The response is abridged and the numbers are illustrative. total is the number of groups.
Apportion statistics to your own area
Section titled “Apportion statistics to your own area”Your area rarely lines up with tract lines. A tract that is 40% inside your polygon should count for 40% of its population. overlap_fraction is the share, from 0 to 1, of each feature’s area that falls inside a reference polygon. Multiply a statistic by it for an area-weighted total:
// People within a 15-minute walk, weighting each tract by how much of it is inside$walk = isochrone(center: point(40.7484, -73.9856), time: 900, mode: "foot");search(dataset("census-tracts")) .intersects($walk) .sum(number(t["population"]) * overlap_fraction($walk));This assumes people are spread evenly across each tract. That’s a reasonable first estimate, and it gets better with smaller geographies. Use census-block-groups for a finer answer.
Over REST, set weight to overlap_fraction and pass the polygon as ref_polygon. Use the same polygon as the spatial filter. For an isochrone, take the geometry from the isochrone response:
curl -X POST https://plaza.fyi/api/v1/features \ -H "x-api-key: $PLAZA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "datasets": ["census-tracts"], "intersects": { "type": "Polygon", "coordinates": [[[-74.02, 40.70], [-73.97, 40.70], [-73.97, 40.75], [-74.02, 40.75], [-74.02, 40.70]]] }, "output": "sum", "expr": "population", "weight": "overlap_fraction", "ref_polygon": { "type": "Polygon", "coordinates": [[[-74.02, 40.70], [-73.97, 40.70], [-73.97, 40.75], [-74.02, 40.75], [-74.02, 40.70]]] } }'overlap_fraction also works in .filter() and .sort(). To see the fraction on each returned feature, use the .overlap_fraction() method in PlazaQL or enrich in REST. Each feature then has an overlap_fraction property:
search(dataset("census-tracts")) .intersects($walk) .overlap_fraction($walk);curl -X POST https://plaza.fyi/api/v1/features \ -H "x-api-key: $PLAZA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "datasets": ["census-tracts"], "intersects": { "type": "Polygon", "coordinates": [[[-74.02, 40.70], [-73.97, 40.70], [-73.97, 40.75], [-74.02, 40.75], [-74.02, 40.70]]] }, "enrich": "overlap_fraction", "ref_polygon": { "type": "Polygon", "coordinates": [[[-74.02, 40.70], [-73.97, 40.70], [-73.97, 40.75], [-74.02, 40.75], [-74.02, 40.70]]] }, "limit": 50 }'overlap_fraction is null for features with no area, such as the ACS point datasets. PlazaQL advanced covers the function in full.
Combine with OpenStreetMap
Section titled “Combine with OpenStreetMap”A dataset and OpenStreetMap can share a query. This finds pharmacies in tracts where more than 20% of households have no vehicle:
$car_free = search(dataset("census-tracts")) .filter(number(t["pct_no_vehicle"]) > 20);
search(node, amenity: "pharmacy").intersects($car_free);The Playground has three census queries ready to run: a county lookup, affluent tracts in a walking radius, and population inside an area.
Limits
Section titled “Limits”- Census datasets are read-only.
- Census statistics are survey estimates. Small areas have wide margins of error, so check
_moebefore you rank tracts by a close value. - Congressional districts carry ACS attributes only when the boundary file and the ACS release cover the same Congress. The TIGER boundaries can be one Congress ahead of the ACS. When they are,
census-congressional-districtsloads without ACS properties until the ACS catches up, whilecensus-congressional-districts-cbfollows the ACS.