Skip to main content
Version: 1.0

Mobile Schools — Integration Guide

For mobile development. Everything needed to power the Education step school picker — state + district dropdowns and the school-name typeahead. No backend changes required.

  • API base URL (local): http://localhost:3001
  • Auth: either an onboardingToken (during signup) OR an accessToken (after login) — both work on every endpoint below. Send as Authorization: Bearer <token>.
  • Send X-Client-Type: mobile on every request.

Short version — just the calls

GET /api/schools/states → list of states
GET /api/schools/districts?state=Maharashtra → districts for that state
GET /api/schools?q=delhi&state=&district=&pinCode=&limit=20
→ school typeahead
GET /api/schools/:id → re-hydrate a previously picked school

Pick any combination — user can drill down state → district → school, or skip both upper dropdowns and search the school name directly. Either path returns the same school payload (with state + district embedded), so the upper dropdowns can be back-filled from the selection.


1 — States dropdown

1.1 — Endpoint

GET /api/schools/states
Authorization: Bearer <onboardingToken | accessToken>

Rate limit: 30 requests / 60 seconds per user. Cache: Cache-Control: public, max-age=3600 — cache aggressively client-side.

Response:

{
"success": true,
"data": [
"Andaman & Nicobar Islands",
"Andhra Pradesh",
"Arunachal Pradesh",
"Assam",
"Bihar",
"...",
],
}

Strings are case-preserved as they appear in the catalog. Pass them back verbatim to /api/schools and /api/schools/districts (server still accepts case-insensitively, but verbatim matches the typeahead labels).


2 — Districts dropdown

2.1 — Endpoint

GET /api/schools/districts?state=Maharashtra
Authorization: Bearer <token>

Rate limit: 60 / 60s. Cache: Cache-Control: public, max-age=3600.

ParamTypeRequiredNotes
statestringyes1–80 chars. Case-insensitive equality match.

400 if state missing: { "code": "MISSING_STATE" }.

Response:

{
"success": true,
"data": ["Ahmednagar", "Akola", "Amravati", "..."],
}

3 — School name typeahead

3.1 — Endpoint

GET /api/schools?q=delhi&state=&district=&pinCode=&limit=20
Authorization: Bearer <token>

Rate limit: 60 / 60s. Cache: Cache-Control: public, max-age=300 — short, per-query.

ParamTypeRequiredNotes
qstringconditional≥ 2 chars. Case-insensitive prefix match on school name (startsWith). Required unless one of the filters below is provided.
statestringnoCase-insensitive equality.
districtstringnoCase-insensitive equality.
pinCodestringnoExact match on PIN string.
limitintno1–50, default 20.

At least one of q/state/district/pinCode is required — full-list dumps are blocked (400 MISSING_FILTER).

q shorter than 2 chars → 400 QUERY_TOO_SHORT.

Response:

{
"success": true,
"data": [
{
"id": "01996c4a-...",
"udiseCode": "27130100102",
"name": "Delhi Public School Pune",
"state": "Maharashtra",
"district": "Pune",
"block": "Haveli",
"management": "Private Unaided",
"category": "Higher Secondary",
"pinCode": "411014",
},
],
}

Sorted alphabetically by name, tie-break by id.

3.2 — All the ways to call it

The three flows the UI supports — pick any:

A. Drill down (state → district → school)

GET /api/schools/states
GET /api/schools/districts?state=Maharashtra
GET /api/schools?state=Maharashtra&district=Pune&q=delhi&limit=20

Most narrow / fastest. State + district filters use the (state, district) btree index.

B. Skip dropdowns, search by name

GET /api/schools?q=delhi&limit=20

Returns matches across all of India. Slower at large scale but acceptable for the prefix-on-name use case.

C. Pin code shortcut

GET /api/schools?pinCode=411014&limit=20

Useful when the user knows their PIN. Combine with q if needed.

3.3 — Auto-populating upper dropdowns from a school pick

Every school object carries state + district. When the user picks a school via path B / C (without choosing state + district first), read those fields off the selected row and write them into the state + district pickers:

const picked = result.data[i];
onboardingDraft.education.state = picked.state;
onboardingDraft.education.district = picked.district;
onboardingDraft.education.schoolId = picked.id;
onboardingDraft.education.schoolName = picked.name; // free-text fallback

4 — Re-hydrate a school by id

When restoring saved onboarding state (mobile sent schoolId previously and wants to render the picker pre-filled):

GET /api/schools/01996c4a-...
Authorization: Bearer <token>

Same school object shape as section 3, wrapped as { success, data }. 404 if the id is unknown.


5 — Edge cases / error codes

HTTPcodeMeaning
400MISSING_FILTER/api/schools called with no q and no filters.
400QUERY_TOO_SHORTq < 2 chars.
400MISSING_STATE/api/schools/districts called without state.
401Token missing / invalid / expired.
404/api/schools/:id — unknown id.
429Rate limit hit. Back off using Retry-After header (seconds).

6 — Sending the picked school back to onboarding

Persist both an opaque schoolId (FK candidate, picked from this catalog) and the human-readable schoolName on the Education object you PATCH to /api/onboarding/session:

PATCH /api/onboarding/session
{
"data": {
"education": [{
"schoolId": "01996c4a-...", // null if user typed a custom name
"schoolName": "Delhi Public School Pune",
"level": "high",
"grade": "10th",
"isCurrent": true,
"city": "Pune",
"country": "India"
}]
},
"checkpoint": "education"
}

Both schoolId and schoolName are persisted on the Education row:

  • schoolId — canonical FK into the School catalog (UDISE). Required if the user picked from the typeahead.
  • schoolName — human-readable label, used for rendering on profile etc. Always required.

When the user typed a school not in the catalog, omit schoolId and send only schoolName. The same shape is accepted by the standalone POST/PUT/PATCH /api/users/me/education endpoints for post-signup edits.


7 — Quick reference cheat sheet

States dropdown → GET /api/schools/states
Districts dropdown → GET /api/schools/districts?state=<state>
School typeahead → GET /api/schools?q=<2+chars>&state=&district=&pinCode=&limit=<1-50>
School re-hydrate → GET /api/schools/:id
Auth header → Authorization: Bearer <onboardingToken | accessToken>