Open-source project
emsifa/api-wilayah-indonesia avatar
emsifa/api-wilayah-indonesia

api-wilayah-indonesia: Indonesian administrative data served as static JSON

Generator API (statis) data wilayah Indonesia, hosted by Github Page

762 stars1,416 forksTypeScriptLicense varies

At a glance

What is it?
This project turns a set of CSV files of Indonesian regions into a static REST API of pre-generated JSON files, covering provinces down to villages, postal codes and boundary polygons. There is no server and no API key, so it hosts on GitHub Pages, and the cost is that you can only fetch what was pre-rendered.
Who is it for?
Adopt this API if you need Indonesian administrative data in a frontend, an offline validation set or a spreadsheet, since the endpoints are plain files behind a CDN with no key, no rate limit and no query language to learn. Steer away if you need filtering by arbitrary attributes or joins across levels, because a static file API can only serve what was pre-generated, and the self-hosting CLI is still marked as coming soon.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The whole idea is that the endpoint is a file

api-wilayah-indonesia generates a REST API of static data for Indonesian regions, and the definition of static is given plainly: the endpoints are just static JSON files, with no server, no API key and no rate limit from the maintainer, so you just fetch. The base URL is https://www.emsifa.com/api-wilayah-indonesia/v2.

The data itself covers the administrative hierarchy of Indonesia, which the description lists as provinces, regencies and cities, districts, villages and urban wards, postal codes, and boundary polygons. The summary endpoint makes the scale concrete:

json
{
  "total_provinces": 38,
  "total_regencies": 514,
  "total_districts": 7285,
  "total_villages": 83762,
  "total_postal_codes": 10632,
  "total_paths": 91238,
  "total_endpoints": 201309
}

Two hundred thousand endpoints, generated once and served as files. The advantage of this shape is the one the documentation names: it can be hosted on static file hosting such as GitHub Pages, Netlify, Cloudflare Pages, Vercel or even Nginx, the process is faster because there is no server, the files are served from a CDN cache, and it is free and simple enough for an MVP, a frontend-only application or an edge function.

The catch is the same shape. A static file API can serve the queries it pre-generated and nothing else, so there is no filtering by arbitrary attributes, no joins and no pagination server side. The second usage mode in the documentation, a download and self-host path with a command line tool, exists to cover the cases the fetch API cannot, and it is marked as coming soon.

CSV in, a Go generator, JSON out

The build pipeline is deliberately boring, and knowing it is what lets you regenerate the data yourself.

The source of truth is a set of CSV files in the data directory, kept in CSV so that they are easy to edit: wilayah.csv holds the hierarchy, wilayah_level_1_2.csv holds richer province and regency data, and wilayah_kodepos.csv maps postal codes. A Go script in the tools directory reads those CSVs and generates thousands of JSON files into the api directory, which ends up containing provinces.json, directories of regencies, districts, villages, postal codes and paths, plus missings.json and stats.json.

The generated api directory is then copied to deploy/v2 alongside the built web application, and the result is pushed to the gh-pages branch, which the workflow file points at. The API is then live, and it is updated automatically every Sunday through a cron schedule of 0 0 * * 0.

So the update cycle is a weekly batch job rather than a live service. That has a direct effect on how you use the data. If you need today's data, you will not get it from this endpoint, and if your application caches responses, the weekly regeneration is the point at which a cached copy would go stale.

The repository layout follows the pipeline: a data directory, a tools directory with the Go generator, a web directory for the site and playground, a cli directory for the download tool, a go.mod declaring a Go 1.25 toolchain under the module name data-wilayah, and a SKILL.md at the root. The SKILL.md file is what the project asks language models to install so that they do not invent region codes, and the documentation points at specific line ranges in it for the level definitions and the id format.

One wrapper for every endpoint, and the level field that tells you what you got

Every endpoint returns the same envelope, which is the kind of consistency that makes a static API pleasant to consume:

json
{
  "data": <object | array>,
  "meta": {
    "updated_at": "2026-09-03",
    "level": 1
  }
}

The data field carries the payload, the meta field carries the generation timestamp in updated_at or generated_at, and the level field carries the depth. The levels are 0 for stats, 1 for province, 2 for regency, 3 for district and 4 for village and postal code.

The id format in this version is worth understanding because it changed. The v2 format uses a dot as the hierarchy separator, unlike the v1 concatenated form, so 11 is a province, 11.01 is a regency, 11.01.01 is a district and 11.01.01.2001 is a village. The number of dots tells you the level, which means a client can work out the depth of a code without a lookup.

Two booleans on each item save you from failed requests. has_path is true when a boundary polygon file exists for that item, and has_latlng can be checked in the missings endpoint. The documentation is explicit about why: check has_path before fetching a polygon so you do not get a 404, and check has_latlng in missings.json. The missings endpoint lists every region where has_path is false or has_latlng is false, both as a flat list and as a summary grouped by level, which makes it straightforward to see how complete the boundary data is before you rely on it for mapping.

The endpoint table, and the fields that only exist at the top two levels

The endpoint surface is a flat list of GET endpoints with no authentication. There is stats.json for the summary, provinces.json for all provinces and provinces/{code}.json for one, regencies/{province_code}.json for the list in a province and regencies/{regency_code}.json for one with its parent province, districts and villages with the same pattern in both directions, postal-codes/{postal_code}.json returning every village that uses that postal code, paths/{code}.json returning the boundary polygon for any level, and missings.json for the gaps.

The important caveat is that the field richness is not uniform. The rich fields named capital, lat, lng, elv, tz, population and total_area only exist at levels 1 and 2, for provinces and regencies. Levels 3 and 4 use a compact form, which means a district record has a different shape from a province record even though both arrive under the same data and meta keys. A client that treats the two the same will read undefined.

A province entry shows the shape at level 1, with the identifier, the name, the capital, coordinates, elevation, timezone offset, population, area and the has_path flag.

The compact form at levels 3 and 4 is not simply smaller. District lists carry the lat and lng plus the has_path flag, and village lists add postal_code, so a client that wants coordinates has to know which level it is looking at. That is the trade of the design: the compact records are far cheaper to serve, and the endpoint count is over two hundred thousand.

A complete specification is published alongside the API as openapi.yml and wilayah-postman.json at the base URL, so a generated client or an import into a request runner is possible without reverse engineering the responses.

A playground, a download tool, and a skill file for language models

Three supporting pieces make the API usable in practice rather than just callable.

The first is the demo and playground at the site, where you pick a province, then a regency, then a district, then a village, and the map zooms automatically while the request URL for what you just clicked is shown in real time. That is a good way to learn the endpoint shapes, because it shows both the request and the result side by side rather than making you guess the parameters from a table.

The second is the download and self-host mode, which the documentation describes as coming soon. The intent is a command that downloads the full dataset in csv, sql or json format into a local file, for cases where you need offline validation, foreign keys or a real query such as WHERE postal_code = ?. The referenced source location in the web application is a component file that the documentation cites by line range, which suggests the command line surface is specified in the interface but the implementation is not finished. As an alternative the documentation points out that you can clone the repository and serve the api directory from your own hosting.

The third is SKILL.md, a file the documentation asks you to install so that your assistant does not fabricate region codes. The rationale is straightforward for a dataset of 83762 villages, where a made-up code is easy to produce and hard to spot. The file documents the level definitions and the id format, and the documentation cites specific line ranges in it for those facts, so the same file serves both a human reader and a model that has been told to read it first.

Taken together, the three pieces cover the three questions a consumer has: what does this look like, how do I get it all, and how do I stop the data being wrong.

Editorial conclusion

Adopt this API if you need Indonesian administrative data in a frontend, an offline validation set or a spreadsheet, since the endpoints are plain files behind a CDN with no key, no rate limit and no query language to learn. Steer away if you need filtering by arbitrary attributes or joins across levels, because a static file API can only serve what was pre-generated, and the self-hosting CLI is still marked as coming soon. Before you build on it, check the missings.json endpoint to see how many regions lack polygons, and read the level metadata before assuming a field is present, since the rich fields such as capital, population and timezone only exist at the province and regency levels.

Frequently asked questions

What is api-wilayah-indonesia and how is it hosted?

It is a static REST API of Indonesian administrative data where each endpoint is a pre-generated JSON file, so there is no server-side scripting, no API key and no rate limit from the maintainer. The base URL is https://www.emsifa.com/api-wilayah-indonesia/v2, and the generated files can be hosted on GitHub Pages, Netlify, Cloudflare Pages, Vercel or Nginx.

How do I fetch the list of provinces from api-wilayah-indonesia?

GET https://www.emsifa.com/api-wilayah-indonesia/v2/provinces.json returns a data array of province objects and a meta object with the level and the generation timestamp. Every endpoint uses the same data and meta wrapper, and no authentication is required.

How do I know whether a region has boundary polygon data?

Each item carries a has_path boolean that is true when GET /paths/{id}.json exists, so you can check it before fetching a polygon and avoid a 404. The missings.json endpoint lists every region where has_path is false or has_latlng is false, with a summary grouped by level.

Can I download the whole dataset or filter it with SQL?

The documentation describes a download and self-host mode as coming soon, with a command that would fetch the full dataset as csv, sql or json for offline validation or complex queries. Until then you can clone the repository and serve the api folder from your own hosting.

Official sources

  1. emsifa/api-wilayah-indonesia on GitHub
  2. Issues
  3. Project website
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/emsifa-api-wilayah-indonesia.svg)](https://hysenlabs.com/projects/emsifa-api-wilayah-indonesia)

Was this useful?