A travel team can plan around holidays by pulling each market's public holidays, the next holiday and the Hijri calendar. Narwhal gives those dates per country and year, and a short script turns them into long breaks.
Find the next public holiday in a market
A booking team plans around the days a market is closed or crowded. The Narwhal API has no demand, booking or price data, and it supplies the dates. Start with the next endpoint, which returns the first holiday on or after a date. This request asks for Indonesia after 3 November 2026 and keeps only the national_public scope.
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/calendars/holidays/IDN/next?from_date=2026-11-03&scope=national_public"{
"country": "IDN",
"coverage": [
{
"scope": "national_public",
"subdivision_codes": []
}
],
"holiday": {
"date": "2026-12-25",
"name": "Christmas Day",
"holiday_key": "christmas_day",
"local_name": "Kelahiran Yesus Kristus",
"scope": "national_public",
"subdivision_codes": []
}
}The holiday object holds the date, the English name, the local_name and the scope. The coverage list shows which scopes the filter kept. Drop the scope filter and the same request returns Christmas collective leave on 24 December 2026 instead, because Indonesia also publishes collective_leave days.
List a year and find the long breaks
Travel demand rises when a holiday joins a weekend. The year endpoint returns every holiday of a country, and a short script can join them with the weekends to find runs of four or more days off. This script reads Indonesia for 2026 without a scope filter, so collective leave days count as days off.
import json
import os
import urllib.request
from datetime import date, timedelta
def get(path):
req = urllib.request.Request(
"https://api.narwhalapi.com" + path,
headers={"Authorization": "Bearer " + os.environ["NARWHAL_API_KEY"]},
)
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
def long_breaks(country, year, min_days=4):
names = {}
for h in get(f"/v1/calendars/holidays/{country}/{year}")["holidays"]:
names.setdefault(date.fromisoformat(h["date"]), []).append(h["name"])
day, end, runs = date(year, 1, 1), date(year, 12, 31), []
while day <= end:
if day.weekday() >= 5 or day in names:
start = day
while day <= end and (day.weekday() >= 5 or day in names):
day += timedelta(days=1)
if (day - start).days >= min_days and any(d in names for d in
(start + timedelta(n) for n in range((day - start).days))):
runs.append((start, day - timedelta(days=1)))
else:
day += timedelta(days=1)
return runs, names
runs, names = long_breaks("IDN", 2026)
for first, last in runs:
days = [first + timedelta(n) for n in range((last - first).days + 1)]
print(first, last, len(days), "days:", "; ".join(dict.fromkeys(n for d in days for n in names.get(d, []))))
It prints four runs for Indonesia in 2026.
| From | To | Days | Holidays inside the run |
|---|---|---|---|
| 2026-02-14 | 2026-02-17 | 4 | Chinese New Year collective leave, Chinese New Year |
| 2026-03-18 | 2026-03-24 | 7 | Nyepi collective leave, Nyepi, Eid al-Fitr collective leave, Eid al-Fitr |
| 2026-05-14 | 2026-05-17 | 4 | Ascension of Jesus Christ, Ascension of Jesus Christ collective leave |
| 2026-12-24 | 2026-12-27 | 4 | Christmas collective leave, Christmas Day |
Change the country code and the year to run the same check for another market. Weekends are Saturday and Sunday in this script, so change that rule for a country with another working week. A team with several markets calls the year endpoint once per country and year, and keeps the runs per country in one table.
Line up the Hijri calendar for Muslim-majority markets
Several holidays in these markets follow the Hijri calendar, so their Gregorian dates move each year. The Hijri endpoint takes a Hijri year (AH) and returns its twelve months with the first day of each. It covers Indonesia, Saudi Arabia, Malaysia, Turkey, Egypt and Pakistan. AH 1447 contains Ramadan 2026, and this request keeps its ninth and tenth months.
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/calendars/hijri/IDN/1447"{
"country": "IDN",
"year": 1447,
"months": [
{
"month": 9,
"calculated_start": "2026-02-18",
"official_start": "2026-02-19",
"status": "published",
"source": "kemenag_table",
"source_url": "https://aceh.kemenag.go.id/storage/berita_informasi/01K7GE96M0ZVRJ62Z520RMYG1B.pdf",
"authority": "Kementerian Agama (Kemenag), Sidang Isbat for Ramadan, Shawwal and Dhu al-Hijjah",
"announced_on": null,
"decision_expected_on": "2026-02-17",
"revised": false,
"updated_at": "2026-11-02T16:31:12.025432Z"
},
{
"month": 10,
"calculated_start": "2026-03-20",
"official_start": "2026-03-21",
"status": "published",
"source": "kemenag_table",
"source_url": "https://aceh.kemenag.go.id/storage/berita_informasi/01K7GE96M0ZVRJ62Z520RMYG1B.pdf",
"authority": "Kementerian Agama (Kemenag), Sidang Isbat for Ramadan, Shawwal and Dhu al-Hijjah",
"announced_on": null,
"decision_expected_on": "2026-03-18",
"revised": false,
"updated_at": "2026-11-02T16:31:12.279058Z"
}
]
}calculated_start is the astronomical first day. official_start is the day the country's authority has set, and it is null until there is one. In this response Ramadan begins on 18 February 2026 by calculation and on 19 February 2026 by the Indonesian table. The tenth month begins on 21 March 2026, which is the date of Eid al-Fitr in the Indonesian holiday list. status says how final the date is.
A Gregorian year touches two Hijri years. The Indonesian table starts AH 1448 on 16 June 2026, so a plan for all of 2026 needs the responses for both 1447 and 1448.
How final is a Hijri date?
Read status. The value calculated means the API has only the computed day and official_start is null. The value published means an official yearly table exists, and such a table can still change. The value announced is the final decision after a moon sighting or isbat meeting.
For AH 1447 the Saudi Arabia response returns calculated for the ninth and tenth months, while Indonesia and Malaysia return published. Treat a calculated or published Eid as a planning date and check it again close to travel.
Does the API have school holidays?
No. The API has no school calendar. Its holiday scopes are national_public, regional_public, bank, federal_government, central_government, collective_leave, public_sector and private_sector, and none of them is a school term. Use the public holiday and long break dates as the calendar signal and bring school dates from your own source.
How do I group the rows that belong to one holiday?
Use holiday_key. In the Indonesian response for 2026, Chinese New Year appears on 16 February as collective leave and on 17 February as a national public holiday, and both rows carry the key lunar_new_year. Group by that key to count a holiday once, and read scope on each row to see which days are the holiday itself and which are the extra days around it.
What does collective leave mean for a trip?
Indonesia lists collective leave days (cuti bersama) beside its national public holidays, with the scope collective_leave. They sit next to a holiday and stretch the break. That is why the 21 March 2026 Eid al-Fitr falls in a seven day run in the table above.
Add scope=national_public to leave those days out. Leave the filter off when you want the whole break, as the script above does.
Published .