A read-only static JSON API for Indonesian schools. No auth, no rate
limit beyond the CDN β just plain fetch() against JSON files.
π§ͺ Open the example app β πΊοΈ Kabupaten map β live demo: filter by region & search by NPSN
| Path | Returns |
|---|---|
| ./index.json | List of provinces [{ kode_provinsi, provinsi, bps_kode_provinsi }] |
| ./meta.json | Generation stats & shard config |
| ./by_region/<prov>/index.json | Kabupaten in a province [{ kode_kabupaten, kabupaten, bps_kode_kabupaten }] |
| ./by_region/<prov>/<kab>/index.json | Kecamatan in a kabupaten [{ kode_kecamatan, kecamatan, bps_kode_kecamatan }] |
| ./by_region/<prov>/<kab>/<kec>/schools.json | Schools in a kecamatan (with npsn), sorted by name |
| ./by_npsn/index.json | How to locate a school by NPSN (shard metadata) |
| ./by_npsn/shards/<shard>.json | { "<npsn>": <full record>, ... } β look up one school |
| ./geojson/kabupaten.json | Admin boundaries keyed by Dapodik kode_kabupaten { "<kode_kabupaten>": { type: "FeatureCollection", features: [...] } } |
There are 311,885 schools β one file each would exceed
hosting limits, so by_npsn is sharded into 4,000
files. The shard id is deterministic and client-computable.
// 1) compute the shard (pad to 4 digits)
const npsn = "20532914";
const shard = String(Number(npsn) % 4000).padStart(4, "0"); // "0914"
// 2) fetch the shard, then index by npsn
const res = await fetch(`/by_npsn/shards/${shard}.json`);
const bucket = await res.json(); // { "<npsn>": <record>, ... }
const school = bucket[npsn]; // the full school record
Live example for NPSN 20532914 β
./by_npsn/shards/0914.json
(then read the "20532914" key).
kode_provinsi: "050000" (Jawa Timur)kode_kabupaten: "056000" (Kota Surabaya)kode_kecamatan: "056020" (Tambaksari)npsn % 4000 β fetch the shard β index by npsn.Open any link above directly in the browser β it's just JSON. A tiny client could look like:
const provinces = await (await fetch("/index.json")).json();
const schools = await (await fetch(
"/by_region/050000/056000/056020/schools.json"
)).json();
console.log(schools.length, "schools in Tambaksari");
./geojson/kabupaten.json
is one static asset whose keys are the Dapodik kode_kabupaten
(six-digit, e.g. "056000" for Kota Surabaya) and whose values are
GeoJSON FeatureCollection objects. Source village/kelurahan
boundaries are grouped by the source KDPKAB and then re-keyed to
the matching school kode_kabupaten by name; source codes with no
Dapodik match are skipped. A code resolves to a renderable polygon set without
a server.
const data = await (await fetch("/geojson/kabupaten.json")).json();
// one city/kabupaten by its Dapodik kode_kabupaten (e.g. Kota Surabaya)
const collection = data["056000"];
console.log(collection.type); // "FeatureCollection"
console.log(collection.features.length); // > 0
console.log(collection.features[0].properties.KDPKAB); // "35.78" (source code)
πΊοΈ Try the Leaflet map β pick a kabupaten and draw its boundaries.
Source & license: boundaries are grouped (not dissolved)
from ardian28/GeoJson-Indonesia-38-Provinsi,
pinned source
( copyright Β© 2024 Ardian Saputra Hasibuan, MIT license). The source advertises
BIG geospatial-service data. Keys are this API's Dapodik kode_kabupaten
(6-digit, e.g. "056000"); each feature keeps its source
KDPKAB (e.g. "35.78").
Region codes in this API are Dapodik codes (050000,
056000, 056020). Each entry in the region index files
also carries the matching dotted BPS/Kemendagri code used by most other
Indonesian datasets, from
region-indonesia.pages.dev:
// ./by_region/050000/056000/index.json
[
{ "kode_kecamatan": "056020", "kecamatan": "Kec. Tambaksari", "bps_kode_kecamatan": "35.78.10" },
...
]
bps_kode_provinsi (e.g. "35"),
bps_kode_kabupaten ("35.78") and
bps_kode_kecamatan ("35.78.10") are matched by name.
They are null when there is no match β e.g. Luar Negeri
(schools abroad) and a few kecamatan whose Dapodik name has no BPS counterpart.
Base URL: https://school-static-api.pesonaedu.workers.dev (CORS-enabled, so browsers can fetch it
cross-origin; PHP can read it with file_get_contents).
const BASE = "https://school-static-api.pesonaedu.workers.dev";
// 1) list all provinces
const provinces = await (await fetch(`${BASE}/index.json`)).json();
// 2) schools in a kecamatan (Jawa Timur > Kota Surabaya > Tambaksari)
const schools = await (await fetch(
`${BASE}/by_region/050000/056000/056020/schools.json`
)).json();
console.log(schools.length, "schools in Tambaksari");
// 3) look up ONE school by NPSN (sharded: npsn % 4000)
const npsn = "20532914";
const shard = String(Number(npsn) % 4000).padStart(4, "0"); // "0914"
const bucket = await (await fetch(`${BASE}/by_npsn/shards/${shard}.json`)).json();
console.log(bucket[npsn]); // full school record
<?php
const BASE = "https://school-static-api.pesonaedu.workers.dev";
function api(string $path): array {
$ctx = stream_context_create(['http' => ['method' => 'GET']]);
$raw = @file_get_contents(BASE . $path, false, $ctx);
return json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
}
// 1) list all provinces
$provinces = api('/index.json');
// 2) schools in a kecamatan
$schools = api('/by_region/050000/056000/056020/schools.json');
echo count($schools) . " schools in Tambaksari\n";
// 3) look up ONE school by NPSN (sharded: npsn % 4000)
$npsn = '20532914';
$shard = str_pad((string)((int)$npsn % 4000), 4, '0', STR_PAD_LEFT); // "0914"
$bucket = api("/by_npsn/shards/{$shard}.json");
print_r($bucket[$npsn]); // full school record