Prerequisites
You need three things:
- Python 3.8 or newer.
- The
requestslibrary. - A free API Ninjas API key — issued instantly, no credit card. If you already have an account, it's on your profile page.
pip install requestsStep 1: Fetch a year of holidays
The endpoint is /v2/holidays. It takes a country (two-letter ISO code) and an optional type filter; here, public_holiday for national days off. Without a year you get the current year, which works on the Free plan. Other years (2005 to 2039) need a premium plan:
import requests
response = requests.get(
'https://api.api-ninjas.com/v2/holidays',
params={'country': 'US', 'type': 'public_holiday'},
headers={'X-Api-Key': 'YOUR_API_KEY'},
timeout=10,
)
response.raise_for_status()
holidays = response.json()
for h in holidays[:3]:
print(f"{h['date']} {h['name']}")The request on the wire and the start of the response:
[
{
"country": "United States",
"iso": "US",
"year": 2026,
"date": "2026-01-01",
"day": "Thursday",
"name": "New Year's Day",
"type": "PUBLIC_HOLIDAY"
},
{
"country": "United States",
"iso": "US",
"year": 2026,
"date": "2026-01-19",
"day": "Monday",
"name": "Martin Luther King Jr. Day",
"type": "PUBLIC_HOLIDAY"
}
]Each entry carries an ISO date, the weekday in day, the holiday name, the country name and its iso code, and a type that classifies it.
Step 2: Filter by holiday type
Not every calendar entry is a day off. The type field separates PUBLIC_HOLIDAY (a national day off) from regional days such as STATE_HOLIDAY and from OBSERVANCE and SEASON entries. Types come back in upper case, and the type filter accepts any case. This small client keeps the year and type optional:
import os
from typing import Optional
import requests
API_URL = 'https://api.api-ninjas.com/v2/holidays'
session = requests.Session()
session.headers['X-Api-Key'] = os.environ.get('API_NINJAS_KEY', 'YOUR_API_KEY')
def get_holidays(country: str, year: Optional[int] = None, kind: Optional[str] = None) -> list:
"""Return one country's holidays for a year (default: the current year),
optionally only one type."""
params = {'country': country}
if year is not None:
params['year'] = year
if kind:
params['type'] = kind
response = session.get(API_URL, params=params, timeout=10)
response.raise_for_status()
return response.json()
for h in get_holidays('US', kind='public_holiday'):
print(f"{h['date']} {h['day']:9} {h['name']}")Step 3: Find upcoming holidays
Dates arrive as ISO strings, so date.fromisoformat() turns the list into something you can compare against today:
from datetime import date
today = date.today()
holidays = get_holidays('US', kind='public_holiday') # the current year
upcoming = [h for h in holidays if date.fromisoformat(h['date']) >= today]
print('Next 5 public holidays:')
for h in upcoming[:5]:
print(f" {h['date']} {h['name']}")Step 4: Build a business-day check
The most common production use of holiday data is one boolean: does this date count? Load the public holidays into a set once, and the check is two conditions:
from datetime import date
YEAR = date.today().year
PUBLIC_HOLIDAYS = {
date.fromisoformat(h['date'])
for h in get_holidays('US', YEAR, kind='public_holiday')
}
def is_business_day(d: date) -> bool:
return d.weekday() < 5 and d not in PUBLIC_HOLIDAYS
print(is_business_day(date(YEAR, 12, 25))) # False: Christmas Day
print(is_business_day(date.today())) # True on a weekday that isn't a holidayFrom here, “add three business days” is a small loop — walk forward one day at a time, counting only dates that pass is_business_day().
Step 5: Handle errors
Failures are the usual three. A 400 carries an error message that says what was wrong, such as an unknown country. Note the caching advice in the 429 branch: a country-year of holidays is a fixed list, so you should rarely request the same one twice:
from requests.exceptions import HTTPError, RequestException
try:
holidays = get_holidays('US')
except HTTPError as error:
status = error.response.status_code
if status == 400:
# Unknown country or type, or a year your plan doesn't include
print('Bad request:', error.response.json().get('error'))
elif status == 401:
print('Missing or invalid API key')
elif status == 429:
print('Rate limit exceeded — cache results, holidays rarely change')
else:
print(f'HTTP {status}: {error.response.text}')
except RequestException as error:
print(f'Network error: {error}')Where to go from here
If business-day math is the end goal, the Working Days API computes it directly. The Country API pairs well when you need metadata about the countries whose calendars you're fetching. The complete script is below.