
Letting users subscribe to Korean public holidays in Google Calendar, Apple Calendar or Outlook means serving an iCalendar (.ics) feed. The hard part isn't the file format — it's having correct holiday dates, including the substitute holidays (대체공휴일) that generic worldwide lists miss. This API supplies those dates; you wrap them in a few lines of VEVENT and serve the .ics.
There is no .ics endpoint here on purpose — an iCal feed is trivial to assemble once you have correct dates, and keeping it in your code lets you brand the calendar name, pick the timezone, and add your own reminders. The pattern: fetch /v1/holidays?year=YYYY (loop a few years so the subscription stays populated), then emit one all-day VEVENT per holiday inside a VCALENDAR wrapper. Because the dataset already includes substitute holidays flagged with substitute: true, your feed is correct on exactly the cases naive holiday calendars get wrong.
| GET | /v1/holidays?year=2026 |
All Korean public holidays for a year (incl. 대체공휴일) — the source rows you turn into VEVENTs. Loop a few years to populate a rolling subscription. |
| GET | /v1/holidays/is?date=2026-09-25 |
Optional: check a single date when you only need one VEVENT or want to verify a feed entry. |
# Fetch a year of holidays, then emit an .ics feed from the rows in your own code.
curl "https://korea-calendar-api.kunstudio.workers.dev/v1/holidays?year=2026"
// API response (excerpt)
{
"year": 2026,
"count": 19,
"holidays": [
{ "date": "2026-09-25", "name": "추석", "name_en": "Chuseok (Korean Thanksgiving)", "substitute": false }
// ... more
]
}
// Build one all-day VEVENT per holiday (JS). DTEND is the day AFTER (exclusive).
// function toICS(holidays) {
// const lines = [
// "BEGIN:VCALENDAR", "VERSION:2.0",
// "PRODID:-//YourApp//Korea Holidays//EN", "CALSCALE:GREGORIAN",
// "X-WR-CALNAME:Korean Public Holidays"
// ];
// for (const h of holidays) {
// const start = h.date.replace(/-/g, ""); // 20260925
// const d = new Date(h.date + "T00:00:00Z");
// d.setUTCDate(d.getUTCDate() + 1);
// const end = d.toISOString().slice(0,10).replace(/-/g, ""); // next day
// lines.push(
// "BEGIN:VEVENT",
// "UID:" + h.date + "-kr-holiday@yourapp",
// "DTSTART;VALUE=DATE:" + start,
// "DTEND;VALUE=DATE:" + end,
// "SUMMARY:" + h.name + " (" + h.name_en + ")",
// "TRANSP:TRANSPARENT",
// "END:VEVENT"
// );
// }
// lines.push("END:VCALENDAR");
// return lines.join("\r\n");
// }
//
// Serve that string with Content-Type: text/calendar; charset=utf-8
No API key is needed on the origin to try this — it is rate-limited for fair evaluation. See the OpenAPI spec for the full schema.
DTSTART;VALUE=DATE with the holiday date and DTEND;VALUE=DATE set to the next day — in iCalendar the end date is exclusive, so a one-day holiday ends the following morning.Content-Type: text/calendar; charset=utf-8. Korean holiday names are UTF-8, so the charset matters for 한글 to display correctly in Google/Apple/Outlook.substitute: true — include them so the feed is correct on the cases generic calendars miss.UID (e.g. date + "-kr-holiday@yourapp") so calendar clients update rather than duplicate events when you refresh the feed. Set TRANSP:TRANSPARENT so holidays don't mark the user as busy.Building a Korean fortune (사주/saju) app? Pair this with the KunStudio Saju API for a full four-pillars reading over REST.