Georgia data, for your code
A read-only REST API over every cleaned, standardized Georgia dataset. Pull rows as JSON, CSV, or Parquet, filtered by year, place, and demographics. No API key, no sign-up.
Base URL
https://georgiacivicdata.org/api/v1- Public & read-only. Every endpoint is a
GET. There is nothing to authenticate. - Contract-driven. Each dataset is generated from a versioned ODCS data contract, so its columns, units, ranges, and allowed values are stable and documented.
- Same data everywhere. The dashboard and MCP server read this same API.
Your first request
Fact data lives at /api/v1/{main_topic}/{topic}. Ask for 2024 composite ACT scores at the district level:
curl "https://georgiacivicdata.org/api/v1/education/act_scores?year=2024&test_component=composite&detail=districts"JSON responses are an envelope: a data array of rows plus a pagination block. Dimension labels (district and school names) are joined in for you.
{
"data": [
{
"year": 2024,
"district_code": "601",
"district_name": "Appling County",
"test_component": "composite",
"num_tested": 234,
"avg_score": 18.7
}
],
"pagination": { "limit": 1000, "offset": 0, "total": 181, "returned": 181 }
}Response formats
Add ?format= to choose how rows come back:
- json (default). Paginated envelope with
data+pagination. - csv. Raw CSV bytes with an
X-Total-Countheader. - parquet. Raw Parquet bytes — ideal for analysis in polars/DuckDB.
Filtering
- Year.
year=2024(exact) oryear_min/year_max(inclusive range). Use one or the other, not both. - Detail level.
detail=states,districts, orschools— whichever the dataset publishes. One dataset documents all of its levels. - Geography & demographics.
district_code,school_code, anddemographictake comma-separated code lists. - Per-column filters. Every categorical column is its own parameter (e.g.
test_component=composite). Comma-separated values are OR within a parameter; multiple parameters AND together.
Each dataset page lists its exact filter parameters and their allowed values.
Pagination
Use limit (default 1000, max 10000) and offset. The pagination.total field always reports the full match count so you can page through deterministically.
Errors & rate limits
Errors return { "error": { "code", "message", "details?" } } with a matching HTTP status — e.g. TOPIC_NOT_FOUND (404), INVALID_FILTER_VALUE (400), RATE_LIMITED (429). The API is generously rate-limited; back off on a 429 and retry.
Discovery
Don’t hard-code dataset shapes — discover them:
- Catalog.
GET /api/v1/datasetslists every dataset and dimension. - Schema.
GET /api/v1/datasets/{main}/{topic}returns columns, filters, ranges, and allowed values as JSON. - Contract.
.../contract?format=yamlreturns the raw ODCS contract.
See the endpoint reference for every route, or browse the dataset catalog.
For AI agents
Point a coding agent at one of these — both are generated from the same contracts:
- llms.txt An index of the API and every dataset.
- llms-full.txt The dense full reference — every column, filter, enum, and range inline.
- openapi.json OpenAPI schema (note: per-column categorical filters are generated per request and not enumerated there — use the contracts or llms-full.txt for those).