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.

ParameterDescription
qRequired. What the user typed (2–100 characters).
stateTwo-letter postal code, e.g. IL.
cityExact city name, any case.
zipFive-digit ZIP code.
kindpublic or private.
include_closedtrue to include closed schools. Default false.
limit1–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_year says which year that is.
  • enrollment is the most recent count, and enrollment_school_year the year it's from. NCES publishes counts after its preliminary directory, so it can be a year behind school_year. location is likewise the most recent one reported.
  • A school missing from NCES's newest list is open: false.
  • grades use PK, KG, 01–12, 13, AE (adult education), and UG (ungraded).
  • Private schools have no district or website.
  • Any field may be null when 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.