{"openapi":"3.1.0","info":{"title":"SolatCal API","version":"2.0.0","summary":"JAKIM e-Solat prayer times for Malaysia as a calendar feed and JSON.","description":"Official JAKIM e-Solat prayer times for all 60 Malaysian zones, unchanged, as an iCalendar feed any calendar app can subscribe to, plus JSON endpoints. Free, with no key and no account. Every /api/* path and /mcp allow 300 requests a minute per client IP, stated on each response in RateLimit-Policy and X-RateLimit-Limit. Times are the property of JAKIM e-Solat (https://www.e-solat.gov.my/index.php?siteId=24&pageId=24); SolatCal is unofficial and not affiliated with JAKIM.","license":{"name":"MIT","identifier":"MIT"}},"externalDocs":{"description":"API and agent docs","url":"https://solatcal.umarrasydan.com/docs"},"servers":[{"url":"https://solatcal.umarrasydan.com"}],"security":[],"paths":{"/api/feed.ics":{"get":{"operationId":"getFeed","summary":"Calendar feed for one zone","description":"An iCalendar feed of prayer times. Subscribe to the URL for a feed that updates itself (use the webcal: scheme for Apple Calendar), or add download=1 for a one-off file.","parameters":[{"name":"zone","in":"query","description":"JAKIM zone code. See /api/zones.","schema":{"type":"string","enum":["JHR01","JHR02","JHR03","JHR04","KDH01","KDH02","KDH03","KDH04","KDH05","KDH06","KDH07","KTN01","KTN02","MLK01","NGS01","NGS02","NGS03","PHG01","PHG02","PHG03","PHG04","PHG05","PHG06","PHG07","PLS01","PNG01","PRK01","PRK02","PRK03","PRK04","PRK05","PRK06","PRK07","SBH01","SBH02","SBH03","SBH04","SBH05","SBH06","SBH07","SBH08","SBH09","SGR01","SGR02","SGR03","SWK01","SWK02","SWK03","SWK04","SWK05","SWK06","SWK07","SWK08","SWK09","TRG01","TRG02","TRG03","TRG04","WLY01","WLY02"]},"required":true,"example":"WLY01"},{"name":"prayers","in":"query","description":"Comma separated prayers to include, any of fajr, syuruk, dhuhr, asr, maghrib, isha.","schema":{"type":"string","pattern":"^(fajr|syuruk|dhuhr|asr|maghrib|isha)(,(fajr|syuruk|dhuhr|asr|maghrib|isha))*$","default":"fajr,dhuhr,asr,maghrib,isha"}},{"name":"align","in":"query","description":"Event start: at the solat time, 5 or 10 minutes before, or snapped down to the containing 15 minute block.","schema":{"type":"string","enum":["exact","early5","early10","block15"],"default":"exact"}},{"name":"duration","in":"query","description":"Event length in minutes. With block15 the end also rounds up to the next quarter hour.","schema":{"type":"integer","minimum":5,"maximum":120,"default":15}},{"name":"busy","in":"query","description":"Show events as busy (TRANSP:OPAQUE) or free (TRANSPARENT).","schema":{"type":"string","enum":["busy","free"],"default":"busy"}},{"name":"alarm","in":"query","description":"Display reminder, that many minutes before the start.","schema":{"type":"string","enum":["none","0","5","10"],"default":"none"}},{"name":"ramadan","in":"query","description":"With 1, Ramadan days gain an Imsak event from imsak to Subuh, and Maghrib is titled as iftar.","schema":{"type":"string","enum":["0","1"],"default":"0"}},{"name":"hijri","in":"query","description":"All-day hijri date events, always free: major occasions only, or every day.","schema":{"type":"string","enum":["none","major","daily"],"default":"none"}},{"name":"lang","in":"query","description":"Language of the labels inside the calendar. Prayer names are Malay either way.","schema":{"type":"string","enum":["en","ms"],"default":"en"}},{"name":"period","in":"query","description":"Date range for a one-off download: month and quarter run from today, year to 31 December. Subscriptions always carry the rest of the year.","schema":{"type":"string","enum":["month","quarter","year"],"default":"year"}},{"name":"download","in":"query","description":"With 1, adds Content-Disposition: attachment.","schema":{"type":"string","enum":["1"]}}],"responses":{"200":{"description":"The calendar. X-Solat-Source is jakim-stale-cache when JAKIM was unreachable.","content":{"text/calendar":{"schema":{"type":"string"}}}},"400":{"description":"A parameter is missing or has a value the API does not accept.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 300 requests in a minute from one client. Retry after the number of seconds in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"JAKIM e-Solat returned nothing usable for this zone and range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/preview":{"get":{"operationId":"getPreview","summary":"Today's events for one zone, as JSON","description":"Takes the same parameters as the feed and applies the same rules.","parameters":[{"name":"zone","in":"query","description":"JAKIM zone code. See /api/zones.","schema":{"type":"string","enum":["JHR01","JHR02","JHR03","JHR04","KDH01","KDH02","KDH03","KDH04","KDH05","KDH06","KDH07","KTN01","KTN02","MLK01","NGS01","NGS02","NGS03","PHG01","PHG02","PHG03","PHG04","PHG05","PHG06","PHG07","PLS01","PNG01","PRK01","PRK02","PRK03","PRK04","PRK05","PRK06","PRK07","SBH01","SBH02","SBH03","SBH04","SBH05","SBH06","SBH07","SBH08","SBH09","SGR01","SGR02","SGR03","SWK01","SWK02","SWK03","SWK04","SWK05","SWK06","SWK07","SWK08","SWK09","TRG01","TRG02","TRG03","TRG04","WLY01","WLY02"]},"required":true,"example":"WLY01"},{"name":"prayers","in":"query","description":"Comma separated prayers to include, any of fajr, syuruk, dhuhr, asr, maghrib, isha.","schema":{"type":"string","pattern":"^(fajr|syuruk|dhuhr|asr|maghrib|isha)(,(fajr|syuruk|dhuhr|asr|maghrib|isha))*$","default":"fajr,dhuhr,asr,maghrib,isha"}},{"name":"align","in":"query","description":"Event start: at the solat time, 5 or 10 minutes before, or snapped down to the containing 15 minute block.","schema":{"type":"string","enum":["exact","early5","early10","block15"],"default":"exact"}},{"name":"duration","in":"query","description":"Event length in minutes. With block15 the end also rounds up to the next quarter hour.","schema":{"type":"integer","minimum":5,"maximum":120,"default":15}},{"name":"busy","in":"query","description":"Show events as busy (TRANSP:OPAQUE) or free (TRANSPARENT).","schema":{"type":"string","enum":["busy","free"],"default":"busy"}},{"name":"alarm","in":"query","description":"Display reminder, that many minutes before the start.","schema":{"type":"string","enum":["none","0","5","10"],"default":"none"}},{"name":"ramadan","in":"query","description":"With 1, Ramadan days gain an Imsak event from imsak to Subuh, and Maghrib is titled as iftar.","schema":{"type":"string","enum":["0","1"],"default":"0"}},{"name":"hijri","in":"query","description":"All-day hijri date events, always free: major occasions only, or every day.","schema":{"type":"string","enum":["none","major","daily"],"default":"none"}},{"name":"lang","in":"query","description":"Language of the labels inside the calendar. Prayer names are Malay either way.","schema":{"type":"string","enum":["en","ms"],"default":"en"}}],"responses":{"200":{"description":"Today's events.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preview"}}}},"400":{"description":"A parameter is missing or has a value the API does not accept.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 300 requests in a minute from one client. Retry after the number of seconds in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"JAKIM e-Solat returned nothing usable for this zone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/zones":{"get":{"operationId":"listZones","summary":"Every JAKIM zone","responses":{"200":{"description":"Zone codes and their districts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Zones"}}}},"429":{"description":"More than 300 requests in a minute from one client. Retry after the number of seconds in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/places":{"get":{"operationId":"listPlaces","summary":"Towns and districts per zone, with coordinates","description":"What the pages use to find a zone by a place name or by the visitor's position.","responses":{"200":{"description":"Places by zone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Places"}}}},"429":{"description":"More than 300 requests in a minute from one client. Retry after the number of seconds in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/health":{"get":{"operationId":"getHealth","summary":"Service and upstream status","responses":{"200":{"description":"Status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}},"429":{"description":"More than 300 requests in a minute from one client. Retry after the number of seconds in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/mcp":{"post":{"operationId":"mcp","summary":"Model Context Protocol server","description":"Stateless Streamable HTTP MCP server speaking JSON-RPC 2.0. Methods: initialize, ping, tools/list, tools/call. Tools: list_zones, get_prayer_times, get_calendar_feed. One request per POST, at most 32 KB.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"const":"2.0"},"id":{"type":["string","integer"]},"method":{"type":"string"},"params":{"type":"object"}}},"example":{"jsonrpc":"2.0","id":1,"method":"tools/list"}}}},"responses":{"200":{"description":"A JSON-RPC response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}},"202":{"description":"A notification was accepted. No body."},"400":{"description":"The body is not JSON, or is a batch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}},"413":{"description":"The body is too large.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}},"429":{"description":"More than 300 requests in a minute from one client. Retry after the number of seconds in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"JsonRpcResponse":{"type":"object","required":["jsonrpc","id"],"description":"A JSON-RPC 2.0 response: either result or error.","properties":{"jsonrpc":{"const":"2.0"},"id":{"type":["string","integer","null"]},"result":{"type":"object","description":"The method result, such as { tools: [...] } or a tool call result with content."},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","description":"-32700 parse error, -32600 invalid request, -32601 unknown method, -32602 unknown tool, -32603 internal error."},"message":{"type":"string"}}}}},"Error":{"type":"object","required":["error","status"],"properties":{"error":{"type":"string","description":"What was wrong, naming the parameter if there was one."},"status":{"type":"integer"}}},"Preview":{"type":"object","description":"One day of a feed: today's events after the same align and duration rules as the ICS feed.","properties":{"zone":{"type":"string"},"description":{"type":"string","description":"JAKIM's district list for the zone."},"date":{"type":"string","format":"date"},"dateLabel":{"type":"string"},"hijri":{"type":["string","null"],"description":"Hijri date as JAKIM publishes it, YYYY-MM-DD."},"hijriLabel":{"type":"string"},"ramadan":{"type":"boolean"},"imsak":{"oneOf":[{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"HH:MM, Malaysia time (UTC+08:00)."},{"type":"null"}]},"events":{"type":"array","items":{"type":"object","properties":{"prayer":{"type":"string","enum":["fajr","syuruk","dhuhr","asr","maghrib","isha","imsak"]},"name":{"type":"string","description":"Malay prayer name."},"summary":{"type":"string","description":"Event title, such as Jumaat on Fridays."},"solat":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"HH:MM, Malaysia time (UTC+08:00)."},"start":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"HH:MM, Malaysia time (UTC+08:00)."},"end":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"HH:MM, Malaysia time (UTC+08:00)."},"crossesMidnight":{"type":"boolean"}}}},"allDay":{"type":"array","items":{"type":"object","properties":{"summary":{"type":"string"},"occasion":{"type":["string","null"]}}}},"tomorrow":{"description":"Tomorrow's raw times. Null on 31 December, as JAKIM publishes one year at a time.","oneOf":[{"type":"object","properties":{"date":{"type":"string","format":"date"},"times":{"type":"object","additionalProperties":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"HH:MM, Malaysia time (UTC+08:00)."}}}},{"type":"null"}]},"stale":{"type":"boolean","description":"True when JAKIM was unreachable and a cached copy was used."}}},"Zones":{"type":"object","description":"Every zone code mapped to the districts JAKIM lists for it.","additionalProperties":{"type":"string"}},"Places":{"type":"object","properties":{"zones":{"type":"object","additionalProperties":{"type":"object","properties":{"state":{"type":"string"},"description":{"type":"string"},"name":{"type":"array","items":{"type":"string"},"description":"What the zone is known as, most searched first."},"places":{"type":"array","description":"Towns and districts as [name, latitude, longitude].","items":{"type":"array","prefixItems":[{"type":"string"},{"type":"number"},{"type":"number"}]}},"radius":{"type":"number","description":"For small special zones: kilometres within which a position belongs to them."}}}},"states":{"type":"object","additionalProperties":{"type":"string"}}}},"Health":{"type":"object","properties":{"ok":{"type":"boolean"},"upstream":{"type":"string","enum":["ok","stale-cache","unreachable","unknown"]},"upstreamUrl":{"type":"string","format":"uri"},"sampleDays":{"type":"integer"},"zones":{"type":"integer"},"serverTime":{"type":"string","format":"date-time"}}}}}}