โ† All guides

๐Ÿ“ก We check Nager.Date (and 75+ other public dev APIs) with a plain GET every 30 minutes โ€” see the live status page. As of publishing it's up and answering normally. This guide is not about availability โ€” it's about what time-dependent endpoints do to your tests.

Nager.Date alternative โ€” the untestable-holiday-branch trap (and the 204-empty-body trap), and how to mock a public-holidays API

Nager.Date is one of the good ones โ€” arguably the best free reference-data API on the internet. Public holidays and long weekends for 200+ countries (we counted the AvailableCountries list: 204), no key, no signup, CORS enabled, no published rate limits, and the whole project is open source so you can self-host it. A paid Pro API now exists to fund the work, and the community API stayed free. Nothing on this page is a complaint about any of that.

The trap isn't the service โ€” it's what happens when your tests call an API whose answers depend on today's date (all of this checked live on September 18, 2026, and confirmed in their own OpenAPI document):

The fix isn't to stop using Nager.Date โ€” it's to stop pointing tests at a clock you don't control. Host a real year of their data verbatim on a free mock API, reproduce their exact URLs, and make "is today a holiday" a scenario you pick instead of a date you wait for. No signup.

1. Host a real year of holidays in one curl

These are the genuine Nager.Date records for PublicHolidays/2026/US (fetched September 18, 2026) with an id added โ€” swap in your countries:

cat > holidays.json <<'EOF'
{
 "holidays": [
  {"id":1,"date":"2026-01-01","localName":"New Year's Day","name":"New Year's Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
  {"id":2,"date":"2026-01-19","localName":"Martin Luther King, Jr. Day","name":"Martin Luther King, Jr. Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
  {"id":3,"date":"2026-02-12","localName":"Lincoln's Birthday","name":"Lincoln's Birthday","countryCode":"US","fixed":false,"global":false,"counties":["US-CA","US-CT","US-IL","US-IN","US-KY","US-MI","US-NY","US-MO","US-OH"],"launchYear":null,"types":["Observance"]},
  {"id":4,"date":"2026-02-16","localName":"Washington's Birthday","name":"Presidents Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
  {"id":5,"date":"2026-04-03","localName":"Good Friday","name":"Good Friday","countryCode":"US","fixed":false,"global":false,"counties":["US-CT","US-DE","US-HI","US-IN","US-KY","US-LA","US-NC","US-ND","US-NJ","US-TN"],"launchYear":null,"types":["Public"]},
  {"id":6,"date":"2026-04-03","localName":"Good Friday","name":"Good Friday","countryCode":"US","fixed":false,"global":false,"counties":["US-TX"],"launchYear":null,"types":["Optional"]},
  {"id":7,"date":"2026-05-08","localName":"Truman Day","name":"Truman Day","countryCode":"US","fixed":false,"global":false,"counties":["US-MO"],"launchYear":null,"types":["School","Authorities"]},
  {"id":8,"date":"2026-05-25","localName":"Memorial Day","name":"Memorial Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
  {"id":9,"date":"2026-06-19","localName":"Juneteenth National Independence Day","name":"Juneteenth National Independence Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
  {"id":10,"date":"2026-07-03","localName":"Independence Day","name":"Independence Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
  {"id":11,"date":"2026-09-07","localName":"Labor Day","name":"Labour Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
  {"id":12,"date":"2026-10-12","localName":"Columbus Day","name":"Columbus Day","countryCode":"US","fixed":false,"global":false,"counties":["US-AL","US-AZ","US-CO","US-CT","US-GA","US-ID","US-IL","US-IN","US-IA","US-KS","US-KY","US-LA","US-ME","US-MD","US-MA","US-MS","US-MO","US-MT","US-NE","US-NH","US-NJ","US-NM","US-NY","US-NC","US-OH","US-OK","US-PA","US-RI","US-SC","US-TN","US-UT","US-VA","US-WV"],"launchYear":null,"types":["Public"]},
  {"id":13,"date":"2026-10-12","localName":"Columbus Day","name":"Columbus Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Bank"]},
  {"id":14,"date":"2026-10-12","localName":"Indigenous Peoples' Day","name":"Indigenous Peoples' Day","countryCode":"US","fixed":false,"global":false,"counties":["US-AK","US-AL","US-CA","US-HI","US-IA","US-LA","US-ME","US-MI","US-MN","US-NC","US-NE","US-NM","US-OK","US-OR","US-SD","US-TX","US-VA","US-VT","US-WI"],"launchYear":null,"types":["Public"]},
  {"id":15,"date":"2026-11-11","localName":"Veterans Day","name":"Veterans Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
  {"id":16,"date":"2026-11-26","localName":"Thanksgiving Day","name":"Thanksgiving Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]},
  {"id":17,"date":"2026-12-25","localName":"Christmas Day","name":"Christmas Day","countryCode":"US","fixed":false,"global":true,"counties":null,"launchYear":null,"types":["Public","Bank"]}
 ]
}
EOF
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/import?name=holidays" \
  -H 'content-type: application/json' --data-binary @holidays.json
# response includes your project id + admin key
# (two warnings about "types"/"counties" are expected โ€” the arrays are kept
#  verbatim in the records, they're just not part of the filterable schema)

Now the questions your app actually asks are query parameters:

# is a given date a holiday? (v3 has no single-date endpoint โ€” your mock does)
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/holidays?date=2026-12-25"
# โ†’ [{"date":"2026-12-25","localName":"Christmas Day",โ€ฆ}]   X-Total-Count: 1

# careful: one date can be several rows โ€” 2026-10-12 is Columbus Day (state),
# Columbus Day (bank) AND Indigenous Peoples' Day. Real data, three records:
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/holidays?date=2026-10-12"
# โ†’ X-Total-Count: 3

# everything in H2, nationwide-only, just the fields the UI needs
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/holidays?date_gte=2026-07-01"
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/holidays?global=true&select=localName,date"

And because it's a real CRUD store, your company's days off can live in the same collection โ€” something no public holidays API will ever host for you:

curl -X POST "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/holidays" \
  -H 'content-type: application/json' \
  -d '{"date":"2026-12-24","localName":"Company day off","name":"Company day off","countryCode":"US","fixed":true,"global":false,"counties":null,"launchYear":null,"types":["Company"]}'

2. Reproduce their exact URLs โ€” and make "today" a base-URL decision

Custom routes serve Nager.Date's actual paths, so switching your app to the mock is a base-URL change and nothing else. The year list at their exact URL, built straight from your import file (no hand-copying):

curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/PROJECT_ID/routes" \
  -H 'x-admin-key: KEY' -H 'content-type: application/json' \
  -d "$(jq -n --arg body "$(jq -c '.holidays | map(del(.id))' holidays.json)" \
        '{method:"GET",path:"/api/v3/PublicHolidays/2026/US",body:$body,contentType:"application/json"}')"

curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/api/v3/PublicHolidays/2026/US"
# โ†’ byte-for-byte the array Nager.Date served on capture day โ€” forever

A frozen NextPublicHolidays โ€” in your tests the next holiday is always Veterans Day, even in July 2031:

curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/PROJECT_ID/routes" \
  -H 'x-admin-key: KEY' -H 'content-type: application/json' \
  -d '{"method":"GET","path":"/api/v3/NextPublicHolidays/:code","body":"[{\"date\":\"2026-11-11\",\"localName\":\"Veterans Day\",\"name\":\"Veterans Day\",\"countryCode\":\"US\",\"fixed\":false,\"global\":true,\"counties\":null,\"launchYear\":null,\"types\":[\"Public\",\"Bank\"]},{\"date\":\"2026-11-26\",\"localName\":\"Thanksgiving Day\",\"name\":\"Thanksgiving Day\",\"countryCode\":\"US\",\"fixed\":false,\"global\":true,\"counties\":null,\"launchYear\":null,\"types\":[\"Public\",\"Bank\"]},{\"date\":\"2026-12-25\",\"localName\":\"Christmas Day\",\"name\":\"Christmas Day\",\"countryCode\":\"US\",\"fixed\":false,\"global\":true,\"counties\":null,\"launchYear\":null,\"types\":[\"Public\",\"Bank\"]}]","contentType":"application/json"}'

And the two branches of IsTodayPublicHoliday โ€” the real API decides by the calendar; your mock decides by the URL prefix. Point the scenario's base URL at /m/PROJECT_ID and today is a holiday; point it at /m/PROJECT_ID/noholiday and it isn't. Same paths below the prefix, empty bodies and all, any day of the year:

# branch you can never trigger on demand for real: 200, empty body
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/PROJECT_ID/routes" \
  -H 'x-admin-key: KEY' -H 'content-type: application/json' \
  -d '{"method":"GET","path":"/api/v3/IsTodayPublicHoliday/:code","status":200,"body":"","contentType":"application/json"}'

# the everyday branch: 204, empty body โ€” the res.json() killer
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/PROJECT_ID/routes" \
  -H 'x-admin-key: KEY' -H 'content-type: application/json' \
  -d '{"method":"GET","path":"/noholiday/api/v3/IsTodayPublicHoliday/:code","status":204,"body":"","contentType":"application/json"}'

curl -i "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/api/v3/IsTodayPublicHoliday/US"            # HTTP 200
curl -i "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/noholiday/api/v3/IsTodayPublicHoliday/US"  # HTTP 204

Now the "we're closed today" banner, the skipped payroll run, and the r.status === 200-not-.json() handling all have a test that runs every day โ€” not once a year.

3. The translation table

Nager.DateYour Mockbird project
/api/v3/PublicHolidays/2026/USsame path via the route above โ€” or /holidays with filters
is this date a holiday? (no endpoint โ€” fetch year, filter client-side)/holidays?date=2026-12-25, answer in X-Total-Count
IsTodayPublicHoliday โ†’ whatever today is200 or 204 โ€” your choice per scenario, via base-URL prefix
NextPublicHolidays โ†’ rolling, rots your assertionsfrozen fixture โ€” deterministic forever
national + regional data for 200+ countriesonly what you seed โ€” see the honest split below
company-specific days off: neverPOST /holidays โ€” one collection for both

4. Rehearse both error shapes

Their exact bodies, captured live โ€” drill that your client parses both (one's a problem object, one's a validation dictionary):

# unknown country โ†’ 404 RFC-7807 problem object
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/PROJECT_ID/routes" \
  -H 'x-admin-key: KEY' -H 'content-type: application/json' --data-binary @- <<'EOF'
{"method":"GET","path":"/api/v3/PublicHolidays/2026/XX","status":404,"body":"{\"title\":\"Unknown country code\",\"status\":404,\"detail\":\"The country code provided is invalid or not recognized.\"}","contentType":"application/json"}
EOF

# unsupported year โ†’ 400 validation dictionary (different shape!)
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/PROJECT_ID/routes" \
  -H 'x-admin-key: KEY' -H 'content-type: application/json' --data-binary @- <<'EOF'
{"method":"GET","path":"/api/v3/PublicHolidays/1066/US","status":400,"body":"{\"title\":\"One or more validation errors occurred.\",\"status\":400,\"errors\":{\"year\":[\"The specified year '1066' is not supported.\"]}}","contentType":"application/json"}
EOF

# and generic resilience: two 503s then success, deterministically
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/holidays?mock_seq=503,503,200&mock_seq_key=w1"

More failure recipes in testing loading & error states.

Where the real API wins โ€” and belongs

Everywhere that isn't a test. Holidays are law, and law changes โ€” a country adds a holiday, moves an observance, and Nager.Date's maintainers ship the update; your fixture from last September won't know. So production lookups belong on the real thing: the free community API within its terms, the Pro API, or a self-hosted instance if you want the data on your infrastructure. The honest split: real calendars for real users โ†’ Nager.Date. Every request where the test needs to control the calendar โ€” local dev, CI, Storybook, demos โ€” โ†’ your import, where December 25th is whenever you say it is.

Start now

The one-curl import in section 1, or zero setup at all:

curl https://mockbird.mockbird.workers.dev/m/demo/products?limit=3

Facts checked live on September 18, 2026: IsTodayPublicHoliday/US answered 204 with an empty body (their OpenAPI documents 200 = "Today is a public holiday", 204 = "Today is not a public holiday", neither with a body schema); PublicHolidays/2026/XX answered 404 {"title":"Unknown country code","status":404,"detail":"The country code provided is invalid or not recognized."}; PublicHolidays/1066/US answered 400 {"title":"One or more validation errors occurred.","status":400,"errors":{"year":["The specified year '1066' is not supported."]}}; Access-Control-Allow-Origin: * confirmed on responses to cross-origin requests; the v3 path list (no single-date endpoint) is from their published OpenAPI; country count 204 from AvailableCountries. Every Mockbird command on this page was run against a live project before publishing (import with the two expected warnings, verbatim Christmas lookup with X-Total-Count 1, the 3-rows-one-date check, range + boolean filters with ?select=, the exact-path year route parse-equal to the live array, both IsToday branches incl. CORS on the 204, both error drills byte-exact, mock_seq 503/503/200, and the company-day POST read-back), and the scratch project was deleted after.

Full API reference in the docs. More guides: Deterministic test data ยท Custom endpoints ยท Testing loading & error states ยท Mock any third-party API. Create your API โ†’

โšก Skip the terminal: this link opens the dashboard's import panel โ€” paste the holidays.json from section 1 and you're done. No signup.