/v1/earthquake/latest cached 60 sThe single latest earthquake: magnitude, depth, coordinates, region, tsunami potential, felt reports and a shakemap URL. Times are UTC.
/v1/earthquake/latestEvery example on this page runs against the real API.
A successful response always has data, meta and attribution.
{
"data": { "magnitude": 5.4, "region": "18 km TimurLaut Pekalongan" },
"meta": { "fetched_at": "2026-02-16T08:30:00Z", "cache_ttl": 60 },
"attribution": "BMKG (Badan Meteorologi, Klimatologi, dan Geofisika)"
}A failed response has error, message and status.
{
"error": "not_found",
"message": "Weather data not available for ADM4 code '99.99.99.9999'.",
"status": 404
}| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | An argument has the wrong format |
| 404 | No data for that code |
| 422 | A required parameter is missing or out of range |
| 429 | Rate limit exceeded. A retry works after one minute |
| 503 | BMKG is unreachable, or this server reached its request budget towards BMKG |
BMKG updates its data at fixed intervals. Every response is cached for as long as its data stays current, on this server and at the CDN in front of it. A request made more often than the cache time receives the same answer.
For heavier use there is a guide to running a separate instance.
/v1/earthquake/latest cached 60 sThe single latest earthquake: magnitude, depth, coordinates, region, tsunami potential, felt reports and a shakemap URL. Times are UTC.
/v1/earthquake/latest/v1/earthquake/recent cached 5 minThe last 15 earthquakes of magnitude 5.0 or more, newest first.
/v1/earthquake/recent/v1/earthquake/felt cached 5 minThe last 15 earthquakes reported as felt, small ones included, with where and how strongly each was felt.
/v1/earthquake/felt/v1/earthquake/nearby cached 5 minEarthquakes from the recent and felt lists within a radius of a point, nearest first, each with distance_km. An empty list means none among BMKG's latest records, not none ever.
lat | required | Latitude, -90 to 90 |
lon | required | Longitude, -180 to 180 |
radius_km | optional | Search radius in km, default 100 |
/v1/earthquake/nearby?lat=-6.2&lon=106.8&radius_km=300/v1/weather/{adm4_code} cached 15 minThe forecast for one village over three days in 3-hour steps: temperature, humidity, weather in Indonesian and English, wind and cloud cover. visibility_m can be null.
adm4_code | in path | Village code as 2.2.2.4 digits, for example 31.74.04.1006 |
/v1/weather/31.74.04.1006/v1/weather/{adm4_code}/current cached 15 minThe forecast entry closest to now for one village. This is BMKG's forecast for the current 3-hour slot, not a station observation.
adm4_code | in path | Village code as 2.2.2.4 digits, for example 31.74.04.1006 |
/v1/weather/31.74.04.1006/current/v1/nowcast cached 2 minProvinces that currently have a severe weather warning, each with a code for the detail endpoint. An empty list means no warning is active.
lang | optional | id (default) or en |
/v1/nowcast/v1/nowcast/warnings cached 2 minEvery active warning in full in one request, each with its affected areas as polygons. meta.missing lists the codes of warnings whose detail could not be read from BMKG, so a short list is not mistaken for a complete one.
lang | optional | id (default) or en |
/v1/nowcast/warnings/v1/nowcast/{alert_code} cached 2 minOne warning in full: event, severity, validity period, description, an infographic URL and the affected areas as polygons. Only codes from the active list exist; any other code returns 404.
alert_code | in path | A code from /v1/nowcast |
lang | optional | id (default) or en |
/v1/nowcast/{alert_code}/v1/nowcast/check cached 2 minActive warnings whose text mentions a place name. has_warnings set to false means no active warning mentions it, which is not a forecast of good weather.
location | required | Name of a district, city or regency as BMKG writes it |
lang | optional | id (default) or en |
/v1/nowcast/check?location=Jakarta/v1/wilayah/search cached 24 hRegions whose name contains the query, at every level, with code, level and full path.
q | required | Part of a name, 2 to 100 characters |
limit | optional | 1 to 100, default 50 |
/v1/wilayah/search?q=tebet&limit=20/v1/wilayah/provinces cached 24 hAll provinces with their two-digit codes.
/v1/wilayah/provinces/v1/wilayah/districts cached 24 hThe regencies (kabupaten) and cities (kota) of one province.
province | required | Two-digit province code, for example 31 |
/v1/wilayah/districts?province=31/v1/wilayah/subdistricts cached 24 hThe districts (kecamatan) of one regency or city.
district | required | Regency or city code, for example 31.74 |
/v1/wilayah/subdistricts?district=31.74/v1/wilayah/villages cached 24 hThe villages (desa and kelurahan) of one district. Their codes are the adm4 codes the weather endpoints take.
subdistrict | required | District code, for example 31.74.04 |
/v1/wilayah/villages?subdistrict=31.74.04The warning detail endpoint returns each affected area as a polygon. Two things often go wrong:
The maps on this site split them as follows:
// One area's polygon is a flat list of [lat, lon] points holding several
// closed rings back to back. A ring ends when a point returns to the ring's
// start, or when the next point is more than 10 km away.
function splitRings(coords) {
const rings = [];
let start = coords[0];
let ring = [coords[0]];
for (let i = 1; i < coords.length; i++) {
const point = coords[i];
if (ring.length >= 4 && distanceKm(ring.at(-1), point) > 10) {
rings.push([...ring, ring[0]]);
start = point;
ring = [point];
continue;
}
ring.push(point);
if (samePoint(point, start) && ring.length >= 4) {
rings.push(ring);
ring = [];
if (i + 1 < coords.length) {
start = coords[i + 1];
ring = [start];
i++;
}
}
}
if (ring.length >= 4) rings.push([...ring, ring[0]]);
return rings;
}
// Real boundaries have short edges. A ring with an edge over 20 km joins
// two distant clusters and is dropped.
const drawable = splitRings(area.polygon).filter((ring) =>
ring.every((point, i) => i === 0 || distanceKm(ring[i - 1], point) <= 20)
);