Authentication
Create a key on the API keys page and send it with every request:
curl "https://school-api-production-0139.up.railway.app/v1/schools/search?q=lincoln+elem&state=IL" \
-H "Authorization: Bearer sk_live_…"
You can also send it as an X-Api-Key header. Keys are secret: call the API from your server, never from a browser.
Limits
- Free: 1,000 requests a month, 60 a minute.
- Pro: 250,000 requests a month, 600 a minute.
Monthly quotas reset on the 1st (UTC) and count across all your keys. Over a limit, you get 429 with a Retry-After header in seconds.
Errors
Errors use standard status codes and a JSON body:
{ "error": { "status": 400, "message": "`state` must be a two-letter postal code, e.g. `IL`." } }
400 bad parameter · 401 missing or invalid key · 403 account inactive · 404 no such school or district · 429 rate limited.
Search schools
GET /v1/schools/search?q=lincoln elem springfield&state=IL
Typo-tolerant search over school names and cities, built for type-ahead "pick your school" fields. Abbreviations like elem, HS, St., and Acad are understood. Best matches come first.
| Parameter | Description |
|---|---|
q | Required. What the user typed (2–100 characters). |
state | Two-letter postal code, e.g. IL. |
city | Exact city name, any case. |
zip | Five-digit ZIP code. |
kind | public or private. |
include_closed | true to include closed schools. Default false. |
limit | 1–50. Default 10. |
Returns { "data": [School, …] }.
Get a school
GET /v1/schools/{id}
The id is the school's NCES id: 12 digits for public schools (e.g. 173451003712), 8 characters for private schools (e.g. A9101234).
A school's history
GET /v1/schools/{id}/history
One record per school year NCES reported the school, newest first: public schools since 2014–15, private schools every other year since 2015–16. Each record has the school's name, address, district (with the name it had that year), grades, charter status, enrollment, and whether it was open, as of that year.
{ "data": [
{ "school_year": "2024-2025", "name": "Lincoln Elementary School", "kind": "public",
"district": { "id": "1734510", "name": "Springfield SD 186" },
"address": { … }, "phone": "(217)555-0100", "website": null,
"location": { "latitude": 39.8, "longitude": -89.65 },
"grades": { "lowest": "PK", "highest": "05" },
"charter": false, "enrollment": 431, "open": true },
{ "school_year": "2023-2024", … }
] }
Search districts
GET /v1/districts/search?q=springfield&state=IL
Takes q, state, include_closed, and limit, as above. Returns { "data": [District, …] }.
Get a district
GET /v1/districts/{id}
The id is the district's 7-digit NCES id (LEAID), e.g. 1734510.
A district's history
GET /v1/districts/{id}/history
One record per school year since 2014–15, newest first, with the district's school_year, name, kind, address, phone, website, and open as of that year.
List a district's schools
GET /v1/districts/{id}/schools?limit=100&offset=0
Alphabetical. limit is 1–100 (default 100). Returns { "data": [School, …], "limit": 100, "offset": 0, "has_more": true }; page with offset while has_more is true.
The School object
{
"id": "173451003712",
"name": "Lincoln Elementary School",
"kind": "public",
"district": { "id": "1734510", "name": "Springfield SD 186" },
"address": { "street": "500 W Main St", "city": "Springfield", "state": "IL", "zip": "62704" },
"phone": "(217)555-0100",
"website": "https://example.org",
"location": { "latitude": 39.8, "longitude": -89.65 },
"grades": { "lowest": "PK", "highest": "05" },
"charter": false,
"enrollment": 431,
"enrollment_school_year": "2024-2025",
"open": true,
"school_year": "2025-2026",
"updated_at": "2026-11-05T00:00:00Z"
}
- The School object is the newest year NCES has published, including preliminary releases.
school_yearsays which year that is. enrollmentis the most recent count, andenrollment_school_yearthe year it's from. NCES publishes counts after its preliminary directory, so it can be a year behindschool_year.locationis likewise the most recent one reported.- A school missing from NCES's newest list is
open: false. gradesusePK,KG,01–12,13,AE(adult education), andUG(ungraded).- Private schools have no
districtorwebsite. - Any field may be
nullwhen the source data doesn't have it. - New fields may be added at any time; existing ones won't change.
The District object
{
"id": "1734510",
"name": "Springfield SD 186",
"kind": "Regular public school district that is NOT a component of a supervisory union",
"address": { "street": "1900 W Monroe St", "city": "Springfield", "state": "IL", "zip": "62704" },
"phone": "(217)555-0000",
"website": "https://example.org",
"open": true,
"school_year": "2023-2024",
"updated_at": "2026-11-05T00:00:00Z"
}
Health
GET /v1/health
No key needed. Returns { "status": "ok" }.
Data
From the U.S. Department of Education's National Center for Education Statistics: the Common Core of Data (public schools and districts), EDGE school locations, and the Private School Universe Survey. school_year says which year a record comes from. Data is refreshed automatically as NCES publishes it.