Working example · Census population change API

Texas population change, from county map to tract drill-down.

Use PrairieCloud to pull ACS population data for every Texas county, compare 2019 vs. 2024, and drill into Denton County census tracts without decoding Census table IDs or maintaining FIPS lookup tables.

ACS 5-Year Estimates254 Texas counties193 Denton County tractsHuman-readable variable: pop_total

Result preview

2019 → 2024

Live API validated

Fastest-growing Texas county

Kaufman County

+39.4% from 2019 to 2024

Largest decline

Kenedy County

-74.5% from 2019 to 2024

Denton County

833,822 → 979,561

+17.5% population change

Denton tract detail

54 grew · 41 shrank

98 lacked a 2019 baseline in the matched response

Outcome snapshot

One workflow. Three useful outputs.

This example uses the same API surface your application would use: REST requests, JSON responses, and stable geography keys. No one-off spreadsheet export. No Census codebook archaeology.

The question

Where did Texas grow from ACS 2019 to ACS 2024, and what happened inside one fast-growing county?

  1. 1Choose a readable variable: pop_total.
  2. 2Resolve every Texas county with geo=county:Texas.
  3. 3Fetch the same geography for ACS 2019 and ACS 2024.
  4. 4Join by stable geo_key and calculate percent change.
  5. 5Drill into Denton County with geo=tract:Denton County, Texas.

Step 1

Pull every Texas county.

Start with the latest ACS 5-Year population estimate for every Texas county. The variable is readable: pop_total. The geography is readable too: county:Texas.

request.sh
curl -H "X-API-Key: $PRAIRIECLOUD_API_KEY" \
  "https://api.prairiecloud.io/v1/data?variables=pop_total&geo=county:Texas&vintage=2024&dataset=acs5"
response-excerpt.json
{
  "data": {
    "county:48001": {
      "geo_key": "county:48001",
      "name": "Anderson County, Texas",
      "geo_type": "county",
      "variables": {
        "pop_total": {
          "api_name": "pop_total",
          "label": "Total population",
          "estimate": 58439,
          "margin_of_error": null
        }
      }
    },
    "county:48121": {
      "geo_key": "county:48121",
      "name": "Denton County, Texas",
      "geo_type": "county",
      "variables": {
        "pop_total": {
          "api_name": "pop_total",
          "label": "Total population",
          "estimate": 979561,
          "margin_of_error": null
        }
      }
    }
  },
  "meta": {
    "source": {
      "dataset": "acs5",
      "vintage_year": 2024,
      "reference_period": "2020-2024"
    },
    "variable_count": 1,
    "geography_count": 254
  }
}

Step 2

Compare 2019 vs. 2024 in Python.

Request the same geography and variable for two ACS vintages, join by geo_key, and calculate change. The response shape does not change between years.

texas_county_change.py
import os
import requests

API_KEY = os.environ["PRAIRIECLOUD_API_KEY"]
BASE_URL = "https://api.prairiecloud.io/v1/data"
HEADERS = {"X-API-Key": API_KEY}


def fetch_texas_county_population(vintage: int):
    response = requests.get(
        BASE_URL,
        headers=HEADERS,
        params={
            "variables": "pop_total",
            "geo": "county:Texas",
            "vintage": vintage,
            "dataset": "acs5",
        },
        timeout=30,
    )
    response.raise_for_status()
    return response.json()["data"]


pop_2019 = fetch_texas_county_population(2019)
pop_2024 = fetch_texas_county_population(2024)

rows = []
for geo_key, current in pop_2024.items():
    previous = pop_2019[geo_key]
    current_pop = current["variables"]["pop_total"]["estimate"]
    previous_pop = previous["variables"]["pop_total"]["estimate"]

    if previous_pop and current_pop is not None:
        pct_change = (current_pop - previous_pop) / previous_pop * 100
        rows.append({
            "county": current["name"],
            "pop_2019": int(previous_pop),
            "pop_2024": int(current_pop),
            "pct_change": pct_change,
        })

rows.sort(key=lambda row: row["pct_change"], reverse=True)

for row in rows[:10]:
    print(
        f"{row['county']}: "
        f"{row['pop_2019']:,} → {row['pop_2024']:,} "
        f"({row['pct_change']:+.1f}%)"
    )
top-counties.txt
Kaufman County, Texas: 123,804 → 172,604 (+39.4%)
Comal County, Texas: 141,642 → 183,826 (+29.8%)
Rockwall County, Texas: 97,175 → 123,617 (+27.2%)
Bastrop County, Texas: 84,522 → 106,582 (+26.1%)
Hays County, Texas: 213,366 → 268,638 (+25.9%)
Chambers County, Texas: 41,305 → 51,498 (+24.7%)
Liberty County, Texas: 83,702 → 103,380 (+23.5%)
Parker County, Texas: 133,811 → 165,168 (+23.4%)
Williamson County, Texas: 547,604 → 672,688 (+22.8%)
Ellis County, Texas: 173,772 → 213,160 (+22.7%)

Step 3

Drill into Denton County tracts.

County-level growth is useful, but most real decisions happen below the county line. Move from county to tract detail using the same variable and the same response shape.

denton-tracts.sh
curl -H "X-API-Key: $PRAIRIECLOUD_API_KEY" \
  "https://api.prairiecloud.io/v1/data?variables=pop_total&geo=tract:Denton%20County,%20Texas&vintage=2024&dataset=acs5"
denton_tract_change.py
def fetch_denton_tract_population(vintage: int):
    response = requests.get(
        BASE_URL,
        headers=HEADERS,
        params={
            "variables": "pop_total",
            "geo": "tract:Denton County, Texas",
            "vintage": vintage,
            "dataset": "acs5",
        },
        timeout=30,
    )
    response.raise_for_status()
    return response.json()["data"]


tracts_2019 = fetch_denton_tract_population(2019)
tracts_2024 = fetch_denton_tract_population(2024)

grew = shrank = no_2019_baseline = 0
changes = []

for geo_key, current in tracts_2024.items():
    current_pop = current["variables"]["pop_total"]["estimate"]
    previous_pop = tracts_2019[geo_key]["variables"]["pop_total"]["estimate"]

    if not previous_pop:
        no_2019_baseline += 1
        continue

    pct_change = (current_pop - previous_pop) / previous_pop * 100
    changes.append((pct_change, current["name"], previous_pop, current_pop))

    if pct_change > 0:
        grew += 1
    elif pct_change < 0:
        shrank += 1

print(f"Existing tracts that grew: {grew}")
print(f"Existing tracts that shrank: {shrank}")
print(f"Tracts without a 2019 population baseline: {no_2019_baseline}")

for pct_change, name, previous_pop, current_pop in sorted(changes, reverse=True)[:5]:
    print(f"{name}: {previous_pop:,.0f} → {current_pop:,.0f} ({pct_change:+.1f}%)")
tract-output.txt
Existing tracts that grew: 54
Existing tracts that shrank: 41
Tracts without a 2019 population baseline: 98

Census Tract 201.09; Denton County; Texas: 7,033 → 12,093 (+71.9%)
Census Tract 204.02; Denton County; Texas: 5,495 → 8,868 (+61.4%)
Census Tract 205.04; Denton County; Texas: 3,506 → 5,365 (+53.0%)
Census Tract 202.04; Denton County; Texas: 4,403 → 6,706 (+52.3%)
Census Tract 218; Denton County; Texas: 3,611 → 5,163 (+43.0%)

Why it matters

The work is the analysis, not the API ceremony.

You could build this directly on the Census Bureau API. PrairieCloud is for teams that would rather spend their time on the product layer.

Raw Census API

  • Table IDs like B01001_001E
  • Geography syntax per level
  • Array responses where position matters
  • FIPS lookup tables and response normalization

PrairieCloud

  • Readable variables like pop_total
  • Readable inputs like county:Texas
  • Named JSON fields with source metadata
  • Stable geography keys as you move county → tract

Build your own

Try the same pattern with your own geography.

Swap county:Texas for another state or tract:Denton County, Texas for another county. The API returns the same structure, so your code does not need to change just because the geography changes.

Build with clean Census data instead of plumbing around it.

Get your free API key

No credit card. 1,000 requests/month on the free tier.