Dokumentasi API dan ejen

Semua yang diperlukan oleh skrip atau pembantu AI untuk menggunakan SolatCal: kalendar, titik akhir JSON dan pelayan MCP. Tiada kunci dan tiada akaun. Rujukan di bawah dalam bahasa Inggeris. Teks yang sama ada di llms.txt, dan spesifikasi OpenAPI menerangkan API HTTP.

Turns official JAKIM e-Solat prayer times for Malaysia into an iCalendar (.ics) feed that any calendar app can subscribe to. 60 zones, no account, no key, free. Unofficial: not affiliated with JAKIM.

All times are Asia/Kuala_Lumpur (UTC+08:00, no DST) and come from JAKIM e-Solat unchanged. SolatCal performs no astronomical calculation of its own.

/api/* and /mcp allow 300 requests a minute per client IP. Static pages are exempt. Responses state the limit in RateLimit-Policy and X-RateLimit-Limit. Exceeding it returns HTTP 429 with Retry-After.

How the times are checked

SolatCal copies JAKIM's published times and calculates none of its own, so there is no calculation method or adjustment that could differ from JAKIM. An automated test suite covers every rule that turns a JAKIM day into events. The published feeds are also checked end to end: a separate script downloads JAKIM's year for every zone and the live feed for the same zone, parses both independently, and compares every event with JAKIM's time for that date and prayer. The latest check, on 2026-09-25 (UTC), compared all 34920 events in the feeds for all 60 zones, from 2026-09-26 to 2026-12-31, with JAKIM's published times: every event matched to the minute and none were missing. See About.

Finding the zone for a place

JAKIM publishes times per zone and names each zone by its districts, not its towns: Subang Jaya, Petaling Jaya and Cyberjaya are all in SGR01, listed by JAKIM as "Petaling" and "Sepang". To go from a place to its times:

  • GET https://solatcal.com/api/find?q=Subang+Jaya returns the zone, with links to its page, today's times and its feed. It knows 524 towns and areas, short forms (KL, PJ, JB, KK), abbreviations (Sg, Bt, Kg) and longer text such as "Shah Alam, Selangor". ?lat=3.07&lon=101.59 finds the zone for a position.
  • Over MCP, get_prayer_times and get_calendar_feed take place instead of zone, and find_zone answers the question on its own.
  • Every zone page lists the towns it covers, and every state has a page with today's times for all its zones, for example https://solatcal.com/prayer-times/selangor.

Pages

Every page below also answers Accept: text/markdown with a Markdown version at the same address (Content-Type: text/markdown, Vary: Accept), 404s included.

Calendar feed

GET https://solatcal.com/api/feed.ics?zone=<ZONE>&...

Returns text/calendar. Subscribe to the URL for a feed that updates itself, or add &download=1 for a one-off file. Swap the scheme for webcal: to make Apple Calendar and most desktop clients subscribe directly. Google Calendar's subscribe deep link also needs the webcal:// form.

ParameterValuesDefaultMeaning
zoneone of the 60 zone codes belowrequiredWhich JAKIM zone
periodmonth, quarter, yearyearDate range for a one-off download. month and quarter run forward from today; year runs to 31 December. Subscriptions always carry the rest of the year regardless
prayerscomma separated, any of fajr, syuruk, dhuhr, asr, maghrib, ishafajr,dhuhr,asr,maghrib,ishaWhich prayers become events
alignexact, early5, early10, block15exactEvent start: at the solat time, 5 or 10 minutes before, or snapped down to the containing 15 minute block
durationinteger 5 to 12015Event length in minutes. With block15 the end also rounds up to the next quarter hour
busybusy, freebusySets TRANSP to OPAQUE or TRANSPARENT
alarmnone, 0, 5, 10noneDisplay reminder, that many minutes before the start
ramadan0, 10With 1, days JAKIM dates to Ramadan gain an Imsak event from imsak to Subuh, and the Maghrib event is titled as iftar
hijrinone, major, dailynoneAll-day hijri date events, marked free: major for dates such as Awal Ramadan and Hari Raya Aidilfitri, daily for every day
langen, msenLanguage of the labels inside the calendar. Prayer names are Malay either way
download1offAdds a Content-Disposition: attachment header

Any unrecognised value returns HTTP 400 with a JSON body of the form {"error": "...", "status": 400} naming the offending parameter.

Examples

Five daily prayers for Kuala Lumpur, on time, 15 minutes:

https://solatcal.com/api/feed.ics?zone=WLY01

Selangor (Gombak and Petaling), snapped to 15 minute blocks, 30 minutes, marked free, 10 minute reminder, Malay labels:

https://solatcal.com/api/feed.ics?zone=SGR01&align=block15&duration=30&busy=free&alarm=10&lang=ms

Kuching with Ramadan events and the major hijri dates:

https://solatcal.com/api/feed.ics?zone=SWK08&ramadan=1&hijri=major

Download the next three months for Johor Bahru including Syuruk:

https://solatcal.com/api/feed.ics?zone=JHR02&period=quarter&prayers=fajr,syuruk,dhuhr,asr,maghrib,isha&download=1

Subscribe from Apple Calendar:

webcal://solatcal.com/api/feed.ics?zone=WLY01

Event shape

One VEVENT per prayer per day. SUMMARY uses the Malay names Subuh, Syuruk, Zohor, Asar, Maghrib, Isyak; on Fridays Zohor is titled Jumaat. UID is <zone>-<YYYYMMDD>-<prayer>@<host> and never changes, so re-importing updates events instead of duplicating them. DESCRIPTION always carries the exact JAKIM time, the zone, the hijri date and a source link, even when align has moved the event start.

With ramadan=1, Ramadan days add an Imsak event (UID suffix imsak) that starts at imsak, ends at Subuh and ignores align. The Maghrib event's title becomes Maghrib (Iftar) or Maghrib (Berbuka). With hijri, all-day events (UID suffix hijri) carry the hijri date. On dates such as Hari Raya the occasion name comes first, as in Hari Raya Aidilfitri ยท 1 Syawal 1448. They are always TRANSPARENT and never carry a reminder.

Other JSON endpoints

The HTTP API is also described as OpenAPI 3.1 at https://solatcal.com/openapi.json, and GET https://solatcal.com/api lists every endpoint with links to these docs.

  • GET https://solatcal.com/api/zones - every zone code mapped to its districts.
  • GET https://solatcal.com/api/find?q=<PLACE> or ?lat=<LAT>&lon=<LON> - the zone for a town, district, state or position, best match first.
  • GET https://solatcal.com/api/places - towns and districts per zone with coordinates, which the pages use to find a zone by name or by the visitor's location.
  • GET https://solatcal.com/api/preview?zone=<ZONE> - today's times for one zone, already run through the same align and duration rules as the feed. Accepts the same parameters as the feed, including ramadan and hijri (all-day events come back under allDay). Also carries today's imsak, whether today is in Ramadan, and tomorrow's raw times under tomorrow, which is null on 31 December.
  • GET https://solatcal.com/api/health - service and upstream status.

MCP server

POST https://solatcal.com/mcp speaks Model Context Protocol over JSON-RPC 2.0. It is stateless Streamable HTTP: no session id, no SSE, and responses are application/json. GET /mcp returns 405. Bodies larger than 32 KB return 413. Batched JSON-RPC arrays are refused. Browsers can call it from any origin.

It serves protocol version 2026-07-28, where every request carries its own version, and the versions before it that open with initialize (2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05).

With 2026-07-28, every request puts io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities in params._meta, and sends the MCP-Protocol-Version and Mcp-Method headers, plus Mcp-Name on tools/call. Handled methods: server/discover, tools/list, tools/call. A missing _meta field is HTTP 400 with JSON-RPC -32602. A header that disagrees with the body is 400 with -32020. An unsupported version is 400 with -32022, listing the supported versions in error.data.supported. An unknown method is 404 with -32601.

With the earlier versions, handled methods are initialize, notifications/* (202, no body), ping, tools/list and tools/call. Anything else is JSON-RPC -32601.

In both, a tools/call for a tool that does not exist is -32602.

ToolPurpose
find_zoneThe zone for a town, district, state, short form or position
list_zonesThe 60 zones grouped by state, optionally filtered by state name or zone prefix
get_prayer_timesOne day's times for a zone or a place, plus the hijri date
get_calendar_feedA validated feed URL, webcal URL, download URL and a plain-language summary, for a zone or a place. Does not fetch times

Bad tool input comes back as a normal result with isError: true and a message naming the offending parameter, not as a protocol error. get_calendar_feed validates through the same rules as the HTTP feed.

curl -sX POST https://solatcal.com/mcp \
            -H 'content-type: application/json' \
            -H 'mcp-protocol-version: 2026-07-28' \
            -H 'mcp-method: tools/list' \
            -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'

Zones

All 60 zones, with the places each is best known for. Each zone page lists every town it covers.

ZoneStateBest-known placesJAKIM districts
JHR01JohorPulau Aur, Pulau PemanggilPulau Aur dan Pulau Pemanggil
JHR02JohorJohor Bahru, Iskandar Puteri, KulaiJohor Bahru, Kota Tinggi, Mersing, Kulai
JHR03JohorKluang, PontianKluang, Pontian
JHR04JohorBatu Pahat, Muar, SegamatBatu Pahat, Muar, Segamat, Gemas Johor, Tangkak
KDH01KedahAlor Setar, Jitra, Kubang PasuKota Setar, Kubang Pasu, Pokok Sena (Daerah Kecil)
KDH02KedahSungai Petani, Kuala Muda, YanKuala Muda, Yan, Pendang
KDH03KedahPadang Terap, SikPadang Terap, Sik
KDH04KedahBalingBaling
KDH05KedahKulim, Bandar BaharuBandar Baharu, Kulim
KDH06KedahLangkawiLangkawi
KDH07KedahPuncak Gunung JeraiPuncak Gunung Jerai
KTN01KelantanKota Bharu, Pasir Mas, TumpatBachok, Kota Bharu, Machang, Pasir Mas, Pasir Puteh, Tanah Merah, Tumpat, Kuala Krai, Mukim Chiku
KTN02KelantanGua Musang, Jeli, LojingGua Musang (Daerah Galas Dan Bertam), Jeli, Jajahan Kecil Lojing
MLK01MelakaMelakaSELURUH NEGERI MELAKA
NGS01Negeri SembilanTampin, JempolTampin, Jempol
NGS02Negeri SembilanKuala Pilah, Rembau, JelebuJelebu, Kuala Pilah, Rembau
NGS03Negeri SembilanSeremban, Nilai, Port DicksonPort Dickson, Seremban
PHG01PahangPulau TiomanPulau Tioman
PHG02PahangKuantan, Pekan, Muadzam ShahKuantan, Pekan, Muadzam Shah
PHG03PahangTemerloh, Jerantut, MaranJerantut, Temerloh, Maran, Bera, Chenor, Jengka
PHG04PahangBentong, Raub, LipisBentong, Lipis, Raub
PHG05PahangBukit Tinggi, Janda Baik, Genting SempahGenting Sempah, Janda Baik, Bukit Tinggi
PHG06PahangCameron Highlands, Genting Highlands, Bukit FraserCameron Highlands, Genting Higlands, Bukit Fraser
PHG07PahangRompin, Endau, Pontian (Rompin)Zon Khas Daerah Rompin, (Mukim Rompin, Mukim Endau, Mukim Pontian)
PRK01PerakTanjung Malim, Tapah, Slim RiverTapah, Slim River, Tanjung Malim
PRK02PerakIpoh, Kuala Kangsar, Batu GajahKuala Kangsar, Sg. Siput , Ipoh, Batu Gajah, Kampar
PRK03PerakGerik, Lenggong, Pengkalan HuluLenggong, Pengkalan Hulu, Grik
PRK04PerakTemenggor, BelumTemengor, Belum
PRK05PerakTeluk Intan, Sitiawan, LumutKg Gajah, Teluk Intan, Bagan Datuk, Seri Iskandar, Beruas, Parit, Lumut, Sitiawan, Pulau Pangkor
PRK06PerakTaiping, Parit Buntar, Bagan SeraiSelama, Taiping, Bagan Serai, Parit Buntar
PRK07PerakBukit LarutBukit Larut
PLS01PerlisKangar, Arau, Padang BesarKangar, Padang Besar, Arau
PNG01Pulau PinangPenang, George Town, ButterworthSeluruh Negeri Pulau Pinang
SBH01SabahSandakan, Sukau, Bukit GaramBahagian Sandakan (Timur), Bukit Garam, Semawang, Temanggong, Tambisan, Bandar Sandakan, Sukau
SBH02SabahBeluran, Telupid, PinangahBeluran, Telupid, Pinangah, Terusan, Kuamut, Bahagian Sandakan (Barat)
SBH03SabahLahad Datu, Semporna, KunakLahad Datu, Silabukan, Kunak, Sahabat, Semporna, Tungku, Bahagian Tawau (Timur)
SBH04SabahTawau, Balong, MerotaiBandar Tawau, Balong, Merotai, Kalabakan, Bahagian Tawau (Barat)
SBH05SabahKudat, Kota Marudu, PitasKudat, Kota Marudu, Pitas, Pulau Banggi, Bahagian Kudat
SBH06SabahGunung KinabaluGunung Kinabalu
SBH07SabahKota Kinabalu, Penampang, PutatanKota Kinabalu, Ranau, Kota Belud, Tuaran, Penampang, Papar, Putatan, Bahagian Pantai Barat
SBH08SabahKeningau, Tambunan, NabawanPensiangan, Keningau, Tambunan, Nabawan, Bahagian Pendalaman (Atas)
SBH09SabahBeaufort, Tenom, SipitangBeaufort, Kuala Penyu, Sipitang, Tenom, Long Pasia, Membakut, Weston, Bahagian Pendalaman (Bawah)
SWK01SarawakLimbang, Lawas, SundarLimbang, Lawas, Sundar, Trusan
SWK02SarawakMiri, Marudi, NiahMiri, Niah, Bekenu, Sibuti, Marudi
SWK03SarawakBintulu, Tatau, SebauhPandan, Belaga, Suai, Tatau, Sebauh, Bintulu
SWK04SarawakSibu, Kapit, MukahSibu, Mukah, Dalat, Song, Igan, Oya, Balingian, Kanowit, Kapit
SWK05SarawakSarikei, Bintangor, JulauSarikei, Matu, Julau, Rajang, Daro, Bintangor, Belawai
SWK06SarawakSri Aman, Betong, SaratokLubok Antu, Sri Aman, Roban, Debak, Kabong, Lingga, Engkelili, Betong, Spaoh, Pusa, Saratok
SWK07SarawakSamarahan, Serian, SimunjanSerian, Simunjan, Samarahan, Sebuyau, Meludam
SWK08SarawakKuching, Bau, LunduKuching, Bau, Lundu, Sematan
SWK09SarawakKampung PatarikanZon Khas (Kampung Patarikan)
SGR01SelangorShah Alam, Petaling Jaya, Subang JayaGombak, Petaling, Sepang, Hulu Langat, Hulu Selangor, S.Alam
SGR02SelangorKuala Selangor, Sabak BernamKuala Selangor, Sabak Bernam
SGR03SelangorKlang, Port Klang, BantingKlang, Kuala Langat
TRG01TerengganuKuala Terengganu, Kuala Nerus, MarangKuala Terengganu, Marang, Kuala Nerus
TRG02TerengganuBesut, SetiuBesut, Setiu
TRG03TerengganuHulu TerengganuHulu Terengganu
TRG04TerengganuKemaman, DungunDungun, Kemaman
WLY01Wilayah PersekutuanKuala Lumpur, PutrajayaKuala Lumpur, Putrajaya
WLY02Wilayah PersekutuanLabuanLabuan

The same list as JSON is at https://solatcal.com/api/zones. Codes are three letters for the state (JHR Johor, KDH Kedah, KTN Kelantan, MLK Melaka, NGS Negeri Sembilan, PHG Pahang, PLS Perlis, PNG Pulau Pinang, PRK Perak, SBH Sabah, SGR Selangor, SWK Sarawak, TRG Terengganu, WLY Wilayah Persekutuan) plus a two digit number.

Attribution

Prayer times are the property of JAKIM e-Solat: https://www.e-solat.gov.my/index.php?siteId=24&pageId=24 SolatCal only reformats them, and is not affiliated with or endorsed by JAKIM. Made by Umar Rasydan.