Documentation

Geo API reference

Catalog-native Bangladesh geography for cascading address fields. Base URL https://geoapi.labmirai.org.

Overview

This service exposes the Bangladesh geo dropdown catalog as HTTP. Each request returns only the children of one parent — do not download the full catalog. Responses use stable catalog IDs as option values.

URLPurpose
https://geoapi.labmirai.orgMarketing portal
https://geoapi.labmirai.org/loginSign in / sign up
https://geoapi.labmirai.org/appControl panel
https://geoapi.labmirai.org/v1/…Keyed catalog API
https://geoapi.labmirai.org/demo/optionsPublic live demo (no key)
https://geoapi.labmirai.org/docsThis page

Authentication

All /v1/* routes require an API key. Create keys in the dashboard (shown once). Send:

X-API-Key: bgc_…

Also accepted: Authorization: Bearer bgc_… or query ?api_key= (prefer the header).

GET /health, GET /demo/options, /docs, and the portal pages do not need a key.

Rate limits & quotas

Address cascade

Always store the returned value (catalog id). On parent change, clear every descendant.

Under a district, next choices mix upazila, city_corporation, and municipality:

GET /v1/options?parent={districtId}&types=upazila,city_corporation,municipality

Example (Dhaka district zilla:3026): Dhaka North / South City Corporation appear alongside upazilas such as Savar.

If you pick a city corporation, continue with wards/thanas (not union → village):

GET /v1/options?parent={cityCorporationId}&types=metropolitan_thana,urban_ward

Rural path after upazila:

GET /v1/options?parent={upazilaId}&types=union
GET /v1/options?parent={unionId}&types=village

District IDs are often zilla:… even though type is district. Use the id the API returns; do not invent codes from names.

After union, only village is exposed. Rural ward and mauza are not available.

Option shape

{
  "options": [
    {
      "value": "division:30",
      "label": "Dhaka",
      "type": "division",
      "parent_id": "country:BD",
      "name_en": "Dhaka",
      "name_bn": "ঢাকা",
      "translated_label_available": true,
      "child_counts": { "district": 13 },
      "cross_source_match": null,
      "boundary_review_required": false,
      "children_mapping_status": null,
      "legacy_only": false
    }
  ]
}

Use value as the select value and next parent. Use label for display (duplicate names are already disambiguated).

Endpoints

GET /health

No key. Returns service status, catalog assemble time, uptime, and whether the public demo is on.

GET /v1/options

/ query
QueryRequiredDescription
parentyesParent catalog id, e.g. country:BD, division:30, zilla:3026
typesnoComma-separated types. Omit to return all allowed children.
langnoen (default) or bn
qnoFilter labels / aliases
includeLegacyno1 to include legacy paths

Allowed types: division, district, upazila, city_corporation, municipality, union, village, urban_ward, metropolitan_thana, cantonment, forest_range.

rural_ward and mauza return 400 if requested.

GET /v1/search

Name/alias search. Query: q (required), optional types, within, limit (max 500), lang, includeLegacy.

GET /v1/search?q=Suapur&types=union&limit=20

GET /v1/path/:id

Ancestor chain from country to the node. 404 if missing or a blocked type.

GET /v1/node/:id

Single node plus path. Same 404 rules as path.

GET /v1/meta

Catalog metadata: coverage, assembled_at, schema_version, country.

GET /demo/options

Same cascade as /v1/options but no API key. Can be disabled from admin settings.

Errors

StatusMeaning
400Missing parent / q, or unsupported types
401Missing or invalid API key
403Key disabled, or public demo disabled
404Unknown id, or blocked type
429IP rate limit, per-key rate limit, or daily quota
503Admin turned the API off

Bodies are JSON: { "error": "…" }, plus quota fields when relevant.

Frontend integration

  1. Load divisions with parent=country:BD&types=division.
  2. On pick, set form field to option value.
  3. Fetch the next level with that id as parent.
  4. After union, fetch villages. If empty, offer free-text “Not listed”.
const res = await fetch(
  'https://geoapi.labmirai.org/v1/options?parent=country:BD&types=division&lang=en',
  { headers: { 'X-API-Key': process.env.GEO_API_KEY } }
);
const { options } = await res.json();

IDs vs names

Catalog ids are the source of truth (division:30, zilla:3026, upazila:302614, union:30261488, village:404147109101036). A village id is one 15-digit number (the old hyphenated BBS path with the hyphens removed). Names are for humans and can collide; the API already appends context to label when needed.

The last code — the village, or whatever deepest id you stored — still resolves to the full chain. For village:404147109101036 (same place as 40-41-47-109-101-036):

{
  "division": "division:40",
  "district": "zilla:4041",
  "upazila": "upazila:404147",
  "union": "union:40414710",
  "village": "village:404147109101036"
}

Not included