Indonesian Region API & Postal Code for Address Dropdown
By Tim APIPedia, Tim Engineering APIPedia · Published · Updated · 10 min read
The Indonesian regional API provides a list of provinces, districts/cities, sub-districts, and postal codes with Ministry of Home Affairs codes for multilevel address forms.

The Indonesian regional API is an HTTP service that returns a list of provinces, districts/cities and sub-districts along with their Ministry of Home Affairs codes, so that address forms can be created in layers without keeping your own regional database. In APIPedia, endpoint /wilayah/* is combined with the postal code API to search for sub-districts from postal codes and vice versa.
This article discusses the available endpoints, the response form, how to create a dropdown for provincial, district, sub-district and postal code addresses, as well as pitfalls that often arise when processing postal codes in Indonesia.
What are the region and zip code API endpoints in APIPedia?
There are six complementary GET endpoints. The first three endpoints form a region hierarchy, the remaining three handle postal codes. All of them use base URL https://api.apipedia.id/v1 and header Authorization: Bearer apk_xxx.
| Endpoints | Function | Parameter |
|---|---|---|
GET /wilayah/provinsi | List of all provinces (38) with code, capital, center coordinates and time zone | - |
GET /wilayah/kabupaten/{kodeProvinsi} | Districts and cities in one province | kodeProvinsi two digits, example 32 |
GET /wilayah/kecamatan/{kodeKabupaten} | Sub-districts in one district/city, complete with a list of postal codes | kodeKabupaten dotted, example 32.73 |
GET /wilayah/kodepos/{kodepos} | Search for sub-districts, districts and provinces from postal codes | kodepos five digits |
GET /kodepos/cari | Look for the postal code from the name of the sub-district or district/city | q (min. 2 characters), limit 1-100, default 20 |
GET /kodepos/{kodepos} | All sub-districts that use one postal code, along with other postal codes in that sub-district | kodepos five digits |
Regional codes follow the Ministry of Home Affairs format: two-digit province ( 32), dotted district/city ( 32.73), and three-segment sub-district ( 32.73.01). This format is consistent across all endpoints, so the code from one response can be immediately used as a parameter for the next request.
How to retrieve the province list via API?
Starting from /wilayah/provinsi. This endpoint does not require parameters and is suitable for being called once when the form page is loaded.
curl https://api.apipedia.id/v1/wilayah/provinsi \
-H "Authorization: Bearer apk_xxx"Responses are wrapped in standard APIPedia envelopes containing is_success, message, and data. The contents are something like this (cut):
{
"is_success": true,
"message": "Daftar provinsi berhasil diambil",
"data": {
"jumlah": 38,
"provinsi": [
{ "kode": "11", "nama": "Aceh", "ibukota": "Banda Aceh",
"lat": 5.57055, "lng": 95.34081, "tz": "WIB" },
{ "kode": "32", "nama": "Jawa Barat", "ibukota": "Bandung",
"lat": -6.90222, "lng": 107.61876, "tz": "WIB" }
]
}
}Provincial field response
| Fields | Type | Information |
|---|---|---|
kode | strings | Two-digit Ministry of Home Affairs provincial code |
nama | strings | Province name |
ibukota | strings | Provincial capital |
lat, lng | number | Province center coordinates |
tz | strings | Time zone: WIB, WITA, or WIT |
Field tz is useful outside of the address form. For example, to display operational hours according to the buyer's time zone, or schedule notifications so that they don't arrive at midnight in eastern Indonesia.
How do you take districts/cities and sub-districts?
Once the user selects the province, send the code to /wilayah/kabupaten/{kodeProvinsi}. Each county item contains kode, nama, ibukota, lat, lng, and elevasi.
curl https://api.apipedia.id/v1/wilayah/kabupaten/32 \
-H "Authorization: Bearer apk_xxx"Then the dotted district code is used for /wilayah/kecamatan/{kodeKabupaten}. The most practical part is here: each sub-district already carries an array of kodepos, so the postal code dropdown doesn't need an additional request.
curl https://api.apipedia.id/v1/wilayah/kecamatan/32.73 \
-H "Authorization: Bearer apk_xxx"{
"kodeKabupaten": "32.73",
"kabupaten": "Kota Bandung",
"jumlah": 30,
"kecamatan": [
{ "kode": "32.73.01", "nama": "Sukasari",
"kodepos": ["40151", "40152", "40153", "40154"] }
]
}Unknown code handling
Province or district codes that do not exist in the data are answered with status 400, not 404. When an incorrect province code is sent to the district endpoint, the error message includes an example of the correct code. So if your app receives code from user input or a URL, treat the 400 as "incorrect input" and display a validation message, not a "not found" page.
How to create a nested address dropdown on a form
Multilevel dropdown (cascading select) is the most common case for APIs in Indonesia: online shop checkout, member registration, KYC forms, and delivery data. The flow is simple because each level only depends on the choices in the previous level.
- When the page loads, call
/wilayah/provinsiand fill in the province dropdown. - When the province changes, clear the dropdown below it, then dial
/wilayah/kabupaten/{kode}. - When the district/city changes, leave the sub-district and postal code blank, then dial
/wilayah/kecamatan/{kode}. - When the sub-district changes, fill in the postal code dropdown from array
kodeposbelonging to that sub-district without a new request. - Save the region code to the database, not just the name, so that the data remains consistent when the display name changes.
Here's an example of JavaScript with fetch. API keys should not be placed in browser code. Direct BASE to a proxy route in your backend (for example /api/apipedia) that adds header Authorization and then forwards the request to APIPedia.
// Proxy backend Anda yang meneruskan ke https://api.apipedia.id/v1
// dengan header Authorization: Bearer apk_xxx
const BASE = "/api/apipedia";
async function api(path) {
const res = await fetch(BASE + path);
const body = await res.json();
if (!res.ok || !body.is_success) {
throw new Error(body.message || "Gagal memuat data wilayah");
}
return body.data;
}
function isiSelect(select, items, label) {
select.innerHTML = `<option value="">Pilih ${label}</option>`;
for (const it of items) {
const opt = document.createElement("option");
opt.value = it.value;
opt.textContent = it.text;
select.append(opt);
}
select.disabled = items.length === 0;
}
const $prov = document.querySelector("#provinsi");
const $kab = document.querySelector("#kabupaten");
const $kec = document.querySelector("#kecamatan");
const $pos = document.querySelector("#kodepos");
let daftarKecamatan = [];
const { provinsi } = await api("/wilayah/provinsi");
isiSelect($prov, provinsi.map(p => ({ value: p.kode, text: p.nama })), "provinsi");
$prov.addEventListener("change", async () => {
[$kab, $kec, $pos].forEach(s => isiSelect(s, [], ""));
if (!$prov.value) return;
const { kabupaten } = await api(`/wilayah/kabupaten/${$prov.value}`);
isiSelect($kab, kabupaten.map(k => ({ value: k.kode, text: k.nama })), "kabupaten/kota");
});
$kab.addEventListener("change", async () => {
[$kec, $pos].forEach(s => isiSelect(s, [], ""));
if (!$kab.value) return;
const data = await api(`/wilayah/kecamatan/${$kab.value}`);
daftarKecamatan = data.kecamatan;
isiSelect($kec, daftarKecamatan.map(k => ({ value: k.kode, text: k.nama })), "kecamatan");
});
$kec.addEventListener("change", () => {
const kec = daftarKecamatan.find(k => k.kode === $kec.value);
const pos = kec ? kec.kodepos : [];
isiSelect($pos, pos.map(p => ({ value: p, text: p })), "kode pos");
});Tips to keep forms fast and save quota
The province list rarely changes, so store the response in the backend cache. The same applies to districts and sub-districts per code. With cache, users who change provinces back and forth do not use up their quota.
This is important in the Free package which provides 500 requests per day with a rate limit of 10 requests per second. Without cache, one form filling can take three requests. With caching on the backend, repeated requests for the same code can simply be served from memory.
How do I find a region from a zip code?
The opposite of dropdown is autofill: the user types in the postal code, then the form fills in the subdistrict, district, and province itself. There are two endpoints for this, and the difference is in the response form and error status.
curl https://api.apipedia.id/v1/kodepos/40115 \
-H "Authorization: Bearer apk_xxx"{
"kodepos": "40115",
"jumlah": 1,
"hasil": [
{
"kecamatan": { "kode": "32.73.09", "nama": "Bandung Wetan" },
"kabupaten": { "kode": "32.73", "nama": "Kota Bandung" },
"provinsi": { "kode": "32", "nama": "Jawa Barat" },
"kodepos": ["40114", "40115", "40116"]
}
],
"catatan": "Kode pos melekat pada kecamatan ..."
}Difference between /postal code/{postal code} and /region/postal code/{postal code}
/kodepos/{kodepos} answers 400 if the format is not five digits and 404 if the postal code is not in the data. Each result also includes all other postal codes in the same sub-district.
/wilayah/kodepos/{kodepos} returns hasil containing objects kecamatan, kabupaten, and provinsi, and answers 400 for a non-existent zip code. Choose the first one if you want to distinguish "incorrect format" from "not found". Choose the second one if you only need the regional hierarchy.
Note that hasil is an array. One postal code can be used in more than one sub-district, so don't just take the first element. If jumlah is more than one, display the subdistrict options to the user.
How to find postal code from place name?
For a free search field, use /kodepos/cari. Parameter q matches the name of the sub-district and the name of the district/city, with a minimum of two characters. If q contains a five digit number, the value is treated as a postal code.
curl "https://api.apipedia.id/v1/kodepos/cari?q=menteng&limit=20" \
-H "Authorization: Bearer apk_xxx"const q = encodeURIComponent("menteng");
const res = await fetch(`https://api.apipedia.id/v1/kodepos/cari?q=${q}&limit=10`, {
headers: { Authorization: "Bearer apk_xxx" },
});
const { data } = await res.json();
for (const h of data.hasil) {
console.log(`${h.kecamatan.nama}, ${h.kabupaten.nama}: ${h.kodepos.join(", ")}`);
}For autocomplete, give a delay (debounce) of about 300 milliseconds before calling the API and use small limit. Users rarely read more than ten suggestions, and fewer requests keep you under the rate limit.
Why don't postal codes reach the sub-district level?
In APIPedia data, postal codes are attached to sub-districts following the Ministry of Home Affairs regional data, not to sub-districts or road sections. A sub-district can have several postal codes, and postal code search responses include catatan that account for this.
The consequence for form design: let the user choose a postal code from a list of subdistricts, then provide free text fields for the subdistrict, street name, and house number. Don't force the postal code to be calculated automatically if the sub-district has more than one postal code.
This regional data is also suitable for combining with other APIs. Address data from KTP results from the KTP OCR API can be matched with the region dropdown, and the name of the sub-district or city can be used when calculating postage via API.
Frequently asked questions
Is the Indonesian API free?
Yes. The Free package at APIPedia is free forever with a quota of 500 requests per day and a rate limit of 10 requests per second. Just register in the console and generate a key in the API Token menu.
What code format is used for districts and sub-districts?
The Ministry of Home Affairs code is dotted: two-digit province ( 32), district/city 32.73, sub-district 32.73.01. Send the district code complete with dots to /wilayah/kecamatan/{kodeKabupaten}.
What happens if the postal code is not found?
/kodepos/{kodepos} answers 404 for non-existent zip codes and 400 for non-five digit formats. /wilayah/kodepos/{kodepos} answers 400 for a non-existent zip code.
Can one postal code lead to several sub-districts?
Can. Therefore field hasil is always an array and accompanied by jumlah. Handle cases of more than one result by asking the user to select the correct subdistrict.
Is it safe to call the API directly from the browser?
It's best not to, because the apk_xxx key will be visible to anyone. Forward requests through your backend, while adding cache so that the daily quota lasts longer.
Get started using region and zip code APIs
The multilevel address dropdown only requires three regional endpoints, and the postal code is already included in the sub-district response. For autofill and search, add /kodepos/{kodepos} and /kodepos/cari.
Create a free account in the APIPedia console, grab the key in the API Token menu, then try the curl example above. The interactive documentation in the console also shows examples of responses for each endpoint.
Topics: #api wilayah #kode pos #data wilayah indonesia #form alamat #tutorial api
${c.about}
Tim APIPedia — Tim Engineering APIPedia
Tim engineering di balik APIPedia yang membangun dan memelihara API data Indonesia - ongkir, OCR KTP, data wilayah, BMKG, hingga server MCP - dan menulis panduan integrasinya dari pengalaman langsung.