GET /hilal
Three endpoints for hilal visibility: the deciding evening is always the 29th of the
current month, the night people actually go looking for the crescent. If the criteria are
not fulfilled at any observation point, the month completes 30 days and the start shifts a
day. Month boundaries come from the
data publik yang dikeluarkan resmi oleh Kementerian Agama RI (publicly available official MABIMS tables) (not Umm al-Qura), and the astronomy uses a multi-site model: the
criteria are evaluated at coastal observation points across Indonesia (currently 25
sites, from Sabang to Rote (see api/data/hilal_sites.json).
GET /api/v1/hilal/info?month={month}&year={year} → JSONGET /api/v1/hilal/viz?month={month}&year={year} → PNG 720×1280GET /api/v1/hilal/map?month={month}&year={year} → PNG 720×1280info carries the numbers + verdict; viz renders a “where to look” sky chart; map renders
the archipelago-wide visibility map. All are public but tightly rate limited
(info 60/hour, viz and map 30/hour each per IP).
Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
month |
int 1–12 | yes | Target Hijri month (the chart shows its deciding evening) |
year |
int | yes | Target Hijri year |
retro |
true / false |
no (default false) |
Unlocks computed retro dates below the curated table (down to 1945-01-01), tagged mabims-retro. See data coverage for the reason why |
Model: “seen anywhere in Indonesia”: the same Neo MABIMS criteria as the computed
table, evaluated at each point’s own local sunset. When the criteria are met, the response
reports the deciding site via deciding_site.
Example
Section titled “Example”curl "https://api.mabims.dev/api/v1/hilal/info?month=11&year=1447"const res = await fetch("https://api.mabims.dev/api/v1/hilal/info?month=11&year=1447");const data = await res.json();console.log("Visible:", data.evening.visible, "at", data.evening.deciding_site?.name);$response = GuzzleHttp\request('GET', 'https://api.mabims.dev/api/v1/hilal/info?month=11&year=1447');$data = json_decode($response->getBody(), true);echo 'Visible: ' . ($data['evening']['visible'] ? 'true' : 'false') . PHP_EOL;{ "input": { "month": 11, "year": 1447 }, "month": { "name": "Dzulqa'dah", "number": 11, "year": 1447, "start": "2026-04-19" }, "previous_month": { "name": "Syawal", "number": 10, "year": 1447, "length": 29 }, "evening": { "hijri_date": "29 Syawal 1447 H", "hijri_day": 29, "gregorian_date": "2026-04-18", "sunset": "18:46", "moonset": "19:31", "moon_alt_deg": 10.36, "moon_az_deg": 289.16, "sun_alt_deg": -0.83, "elongation_deg": 14.52, "illumination_pct": 1.44, "age_hours": 23.9, "deciding_site": { "name": "Sabang / Weh Island", "lat": 5.8897, "lon": 95.3164, "elev_m": 16.0, "tz": "Asia/Jakarta" }, "sites_checked": 25, "alt_ok": true, "elong_ok": true, "visible": true }, "source": "mabims", "warnings": []}Visibility criteria
Section titled “Visibility criteria”visible is true when the criteria are met at at least one observation point:
- Moon altitude ≥ 3.0°: topocentric (the hilal’s position relative to the observer), with atmospheric refraction applied (Bennett formula);
- Elongation ≥ 6.4°: geocentric (the Indonesian hisab convention), the moon–sun angular separation;
(See the official MUI explainer and the government announcement via Setkab for the source of these criteria.)
both evaluated at that site’s local sunset on day 29. The further west a point sits, the
higher the hilal stands at its sunset, so comfortable months are usually decided on the
western coast (Aceh), while months with a southerly lunar declination can be decided along
the southern arc (Java → Lesser Sunda). deciding_site is always present: when the
criteria are met nowhere, the reported site is the best-margin one (closest to
fulfilment), all altitude, elongation and time values refer to that site.
Sunset/moonset times, azimuths, illumination and lunar age are computed topocentrically at the deciding site (not Sabang), and times are displayed in the site’s own timezone (WIB/WITA).
Chart (/hilal/viz)
Section titled “Chart (/hilal/viz)”A 720×1280 vertical PNG: sunset sky with the crescent (bright limb facing the sun), a
verdict pill (MEMENUHI KRITERIA / TIDAK MEMENUHI / MENDEKATI BATAS / DI BAWAH HORIZON) and a
PARAMETER · MABIMS MIN · STATUS criteria table plus a TITIK PENGAMAT row, the sky scene,
criteria values and times all refer to the same deciding site. Output is deterministic per
parameter.
Map (/hilal/map)
Section titled “Map (/hilal/map)”A 720×1280 PNG of the archipelago visibility region: the area where the Neo MABIMS criteria are met at local sunset, the altitude-3° and elongation-6.4° boundary lines, and 95 display points (green = meets the criteria, gray = does not). The deciding point from the 25-site model is marked with a yellow ring.
The card’s table uses the same deciding point as /hilal/viz, plus the TITIK MEMENUHI /
TITIK TIDAK MEMENUHI counts from the 95 points, so both cards always name the same observer.
Rate limit: 30/hour per IP.
Caching
Section titled “Caching”Cache-Control: public, max-age=86400, s-maxage=86400: deterministic per parameter, publicly CDN-cached (CDN gets one render per edge location per day).- Rendering is CPU work: request only what you need and respect the rate limits.
- Render cap: Hijri 1444–1475: PNG cards are only served in that range (see
hilal_image_rangeon/meta); outside it the endpoint refuses without_of_coverage. The date-conversion data cap remainscoverage.forward_ceil.
Every response also carries an ETag. Send If-None-Match: <etag> and the server answers 304 Not Modified (empty body) when the data has not changed, with the same Cache-Control and ETag headers.
Errors
Section titled “Errors”| Code | HTTP | Cause |
|---|---|---|
out_of_coverage |
400 | Month/year outside table coverage (see /meta) |
computation_unavailable |
503 | Astronomy computation failed |
render_failed |
500 | PNG rendering failed |
invalid_retro |
400 | retro param is not a boolean value (accepts true/false, 1/0) |
invalid_bare |
400 | bare is not a boolean value (accepts true/false, 1/0) |
invalid_download |
400 | download is not a boolean value (accepts true/false, 1/0) |
rate_limit_exceeded |
429 | Too many requests; wait per the Retry-After header (seconds) |
not_found |
404 | Unknown path (uniform error envelope) |
Boolean flags accept true/false (case-insensitive) and 1/0; absent = false.
