Skip to main content

Geographic Boundaries

Build choropleth maps, run spatial analysis, and visualize Census data — all from the PrairieCloud API. The boundary endpoints deliver GeoJSON shapes for every geography we cover, at multiple resolution tiers.

The killer feature: add include_geometry=true to any /v1/data request and get data and boundary shapes in a single API call. One request, one response, one map.


Available Geography Types

PrairieCloud serves 331,559 GeoJSON boundaries across all 7 geography levels:

TypeDescriptionBoundary CountTier Required
nationUnited States1Free
stateU.S. states + DC + territories56Free
countyCounties and county equivalents3,243Pioneer
metroMetropolitan and micropolitan statistical areas939Pioneer
cdCongressional districts (118th Congress)436Pioneer
tractCensus tracts85,058Pro
block_groupCensus block groups241,854Business
Tract and block group boundaries

Tract boundaries (85,058) are available on the Pro plan and above. Block group boundaries (241,854) require a Business plan or higher. Both include boundaries for ACS 5-Year vintages 2017–2024.


Resolution Tiers

Boundary files are available at three resolution tiers, matching the U.S. Census Bureau's cartographic boundary file conventions:

ResolutionBest ForFile Size (states)Tier Access
20m (default)Web maps, dashboards, interactive visualizations~1 MBFree / Pioneer+
5mDetailed maps, print-quality at regional scale~5 MBPro+
500kHigh-fidelity print, enterprise cartography~25 MBEnterprise
Start with 20m

The 20m resolution is visually identical to 5m at typical web zoom levels and loads significantly faster. Only upgrade when you need fine coastal or border detail.


Endpoints

Get boundaries by type

GET /v1/boundaries/{geo_type}

Returns a GeoJSON FeatureCollection containing all geographies of the specified type.

Path parameters:

ParameterTypeDescription
geo_typestringOne of: nation, state, county, metro, cd, tract, block_group

Query parameters:

ParameterTypeDefaultDescription
statestringFilter by state FIPS code (e.g., 48 for Texas)
resolutionstring20mResolution tier: 20m, 5m, or 500k
vintagestring2024Boundary vintage year

Examples:

# All counties in Texas
curl -H "X-API-Key: YOUR_KEY" \
"https://api.prairiecloud.io/v1/boundaries/county?state=48&resolution=20m"

# All census tracts in Harris County, Texas
curl -H "X-API-Key: YOUR_KEY" \
"https://api.prairiecloud.io/v1/boundaries/tract?state=48&county=201"

# All block groups in a specific county (Business tier)
curl -H "X-API-Key: YOUR_KEY" \
"https://api.prairiecloud.io/v1/boundaries/block_group?state=48&county=201"

Get a single boundary

GET /v1/boundaries/{geo_key}

Returns a single GeoJSON Feature for a specific geography.

Path parameters:

ParameterTypeDescription
geo_keystringGeography key (e.g., state:48, county:48201, tract:48201311100, block_group:482013111001)

Examples:

# A county boundary
curl -H "X-API-Key: YOUR_KEY" \
"https://api.prairiecloud.io/v1/boundaries/county:48201"

# A census tract boundary
curl -H "X-API-Key: YOUR_KEY" \
"https://api.prairiecloud.io/v1/boundaries/tract:48201311100"

# A block group boundary (Business tier)
curl -H "X-API-Key: YOUR_KEY" \
"https://api.prairiecloud.io/v1/boundaries/block_group:482013111001"

Get centroids

GET /v1/centroids/{geo_type}

Returns lightweight GeoJSON Point geometries — the geographic center of each boundary. Ideal for dot maps, label placement, and marker clustering.

Path parameters:

ParameterTypeDescription
geo_typestringOne of: nation, state, county, metro, cd, tract, block_group

Query parameters:

ParameterTypeDefaultDescription
statestringFilter by state FIPS code

Examples:

# County centroids in Texas
curl -H "X-API-Key: YOUR_KEY" \
"https://api.prairiecloud.io/v1/centroids/county?state=48"

# Tract centroids in Harris County
curl -H "X-API-Key: YOUR_KEY" \
"https://api.prairiecloud.io/v1/centroids/tract?state=48&county=201"

Data + shapes in one call

GET /v1/data?include_geometry=true

This is the fastest path from API call to map. Add include_geometry=true to any /v1/data request and each data point includes its full GeoJSON geometry. No second request needed.

Requires: Pro tier or above.

Example:

# County-level population map of Texas
curl -H "X-API-Key: YOUR_KEY" \
"https://api.prairiecloud.io/v1/data?vintage=2024&variables=pop_total&geo=county:48*&include_geometry=true"

# Tract-level median income in Harris County
curl -H "X-API-Key: YOUR_KEY" \
"https://api.prairiecloud.io/v1/data?vintage=2024&variables=income_median_household&geo=tract:48201*&include_geometry=true"

Each data point in the response includes a geometry field:

{
"data": {
"tract:48201311100": {
"name": "Census Tract 3111, Harris County, Texas",
"variables": {
"income_median_household": {
"estimate": 72150,
"margin_of_error": 8420
}
},
"geometry": {
"type": "MultiPolygon",
"coordinates": [[[[-95.4, 29.7], ...]]]
}
}
}
}

Bulk boundary delivery

The public API serves boundary data through /v1/boundaries/{identifier} for single features and filtered collections. PrairieCloud does not expose a public static bulk-download endpoint.

Enterprise customers can arrange custom bulk boundary delivery for approved use cases, including higher-resolution 500k cartographic boundaries where available.


Tier Access

FeatureFreePioneerProBusinessEnterprise
Boundary and centroid typesnation, state+ county, metro, cd+ tract+ block_groupAll
Resolution tiers20m only20m only20m, 5m20m, 5m20m, 5m, 500k
include_geometry on /v1/dataThrough tractThrough block groupThrough block group
Custom bulk boundary delivery
Centroidsnation, state+ county, metro, cd+ tract+ block_groupAll

Boundary and centroid requests are free — they cost 0 units against your monthly quota for every tier that has access.


Response Format

All boundary responses conform to GeoJSON RFC 7946.

Collection response (/v1/boundaries/{geo_type}):

{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "state:48",
"properties": {
"geo_key": "state:48",
"name": "Texas",
"fips": "48",
"geo_type": "state"
},
"geometry": {
"type": "MultiPolygon",
"coordinates": [[[[-106.6, 31.8], [-106.5, 31.8], ...]]]
}
}
]
}

Single feature response (/v1/boundaries/{geo_key}):

{
"type": "Feature",
"id": "tract:48201311100",
"properties": {
"geo_key": "tract:48201311100",
"name": "Census Tract 3111, Harris County, Texas",
"fips": "48201311100",
"geo_type": "tract",
"state_fips": "48",
"county_fips": "48201"
},
"geometry": {
"type": "MultiPolygon",
"coordinates": [[[[-95.4, 29.7], [-95.3, 29.7], ...]]]
}
}

Response headers:

HeaderDescription
X-Boundary-VintageBoundary vintage year (e.g., 2024)
Content-Typeapplication/geo+json
Cache-Controlpublic, max-age=604800, s-maxage=2592000 (7-day browser, 30-day CDN)
ETagContent hash for conditional requests

Caching

Boundary data changes infrequently (typically once per year with new Census releases). The API sets aggressive cache headers:

  • Browser cache: 7 days (max-age=604800)
  • CDN cache: 30 days (s-maxage=2592000)
  • ETag/If-None-Match: Send the ETag value back as If-None-Match on subsequent requests. If the boundary hasn't changed, you'll receive a 304 Not Modified with zero body — saving bandwidth and latency.
# First request — get the ETag
curl -i -H "X-API-Key: YOUR_KEY" \
"https://api.prairiecloud.io/v1/boundaries/state"
# Response includes: ETag: "abc123..."

# Subsequent request — conditional
curl -H "X-API-Key: YOUR_KEY" \
-H 'If-None-Match: "abc123..."' \
"https://api.prairiecloud.io/v1/boundaries/state"
# Returns 304 Not Modified if unchanged

Code Examples

Python: Tract-level choropleth map

Build a choropleth map of median household income by tract in Harris County with a single API call:

import requests
import plotly.express as px
import pandas as pd

API_KEY = "your_key_here"

# One call: data + shapes
resp = requests.get(
"https://api.prairiecloud.io/v1/data",
params={
"vintage": "2024",
"variables": "income_median_household",
"geo": "tract:48201*",
"include_geometry": "true",
},
headers={"X-API-Key": API_KEY},
)
data = resp.json()

# Build GeoJSON for plotly
features = []
values = []
for geo_key, point in data["data"].items():
income = point["variables"]["income_median_household"]["estimate"]
if income is not None:
features.append({
"type": "Feature",
"id": geo_key,
"properties": {"name": point["name"]},
"geometry": point["geometry"]
})
values.append({"geo_key": geo_key, "income": income})

geojson = {"type": "FeatureCollection", "features": features}
df = pd.DataFrame(values)
fig = px.choropleth(df, geojson=geojson, locations="geo_key",
featureidmap=lambda x: x["id"],
color="income",
title="Median Household Income by Tract — Harris County, TX")
fig.show()

JavaScript: Leaflet map with tracts

const response = await fetch(
'https://api.prairiecloud.io/v1/boundaries/tract?state=48&county=201',
{ headers: { 'X-API-Key': 'your_key_here' } }
);
const geojson = await response.json();
L.geoJSON(geojson, {
style: { weight: 1, color: '#666', fillOpacity: 0.3 }
}).addTo(map);

R: Plot tract boundaries

library(sf)
library(httr2)

resp <- request("https://api.prairiecloud.io/v1/boundaries/tract") |>
req_url_query(state = "48", county = "201") |>
req_headers("X-API-Key" = "your_key_here") |>
req_perform()

tracts <- st_read(resp_body_string(resp))
plot(st_geometry(tracts), main = "Census Tracts — Harris County, TX")

Next Steps