Public holidays in Chile, Colombia, or Mexico are well-documented and updated by someone else. Your company’s own non-working days are not. Annual plant maintenance, industry trade fairs, collective vacation periods, end-of-year shutdowns — these need to live somewhere, and in most companies that somewhere is a hardcoded array, an environment variable, or a spreadsheet that HR updates once a year and nobody else remembers to check.
The result is predictable: a cron job that fires during a plant shutdown, a delivery date that lands on collective vacations, an SLA deadline calculated without accounting for a company-wide closure.
The standard approach and why it breaks
Most teams end up with something like this:
const COMPANY_CLOSURES = [ "2026-07-15", // Annual maintenance "2026-12-26", // Year-end closure "2026-12-27", "2026-12-28", ];function isWorkingDay(date, country) { if (COMPANY_CLOSURES.includes(date)) return false; // … official holiday logic }
The problem is not the logic — it’s the coupling. These dates live in the codebase, which means:
Updating next year's closures requires a deploy
Multiple services (backend, workers, payroll scripts) each keep their own copy that diverges over time
There's no clear audit trail for changes
The array needs to be duplicated for each country your operations cover
An environment variable or internal database doesn’t solve the coordination problem — it just moves it.
A cleaner approach: custom calendars via API
The idea: define a calendar with a name and a slug, add your company’s non-working dates to it, then use ?calendar=slug as an overlay parameter on any business day calculation. Official country holidays still apply — your calendar adds on top.
Create the calendar
curl -X POST https://api.feriados.io/v1/calendars \
-H "Authorization: Bearer frd_your_key" \
-H "Content-Type: application/json" \
-d '{"name": "Plant North", "slug": "plant-north"}'
{
"success": true,
"data": {
"id": "01HZ...",
"name": "Plant North",
"slug": "plant-north",
"created_at": "2026-04-04T12:00:00.000Z"
}
}
Add non-working dates
curl -X POST https://api.feriados.io/v1/calendars/plant-north/dates \
-H "Authorization: Bearer frd_your_key" \
-H "Content-Type: application/json" \
-d '{
"dates": [
{ "date": "2026-07-15", "name": "Annual maintenance" },
{ "date": "2026-12-26", "name": "Year-end closure" },
{ "date": "2026-12-27", "name": "Year-end closure" },
{ "date": "2026-12-28", "name": "Year-end closure" }
]
}'
Up to 100 dates per request. Duplicates are silently ignored, so running the same script twice is safe.
Use the calendar in calculations
Once the calendar is set up, any business day endpoint accepts ?calendar=slug:
curl "https://api.feriados.io/v1/CL/is-business-day?date=2026-07-15&calendar=plant-north" \ -H "Authorization: Bearer frd_your_key"
{
"success": true,
"data": {
"date": "2026-07-15",
"is_business_day": false,
"day_of_week": "Wednesday",
"region": null
},
"meta": { "country": "CL", "calendar": "plant-north", "total": 1 }
}
Wednesday, July 15 has no official Chilean holiday — but the API returns false because it’s registered as a plant closure. Without ?calendar, it would return true.
Practical examples
Cron jobs that skip company shutdowns
const API = "https://api.feriados.io/v1";
const HEADERS = { Authorization: `Bearer ${process.env.FERIADOS_API_KEY}` };
async function isWorkingDayToday(country, calendar) {
const today = new Date().toISOString().slice(0, 10);
const url = ${API}/${country}/is-business-day?date=${today}&calendar=${calendar};
const { data } = await fetch(url, { headers: HEADERS }).then(r => r.json());
return data.is_business_day;
}
if (await isWorkingDayToday(“CL”, “plant-north”)) {
await processPayroll();
} else {
console.log(“Plant closed — skipping payroll run”);
}
Delivery dates that avoid company closures
async function estimatedDelivery(country, calendar, businessDays = 3) {
const today = new Date().toISOString().slice(0, 10);
const url = `${API}/${country}/business-days/add?date=${today}&days=${businessDays}&calendar=${calendar}`;
const { data } = await fetch(url, { headers: HEADERS }).then(r => r.json());
return data.result_date;
}
const delivery = await estimatedDelivery(“CL”, “plant-north”, 3);
// → “2026-07-21” instead of “2026-07-17”
SLA deadlines that account for operational closures
async function slaDeadline(country, calendar, openedAt, slaDays) {
const url = `${API}/${country}/business-days/add?date=${openedAt}&days=${slaDays}&calendar=${calendar}`;
const { data } = await fetch(url, { headers: HEADERS }).then(r => r.json());
return data.result_date;
}
const deadline = await slaDeadline(“CL”, “plant-north”, “2026-07-13”, 5);
// → “2026-07-22” instead of “2026-07-20”
Multiple operations, multiple calendars
If your company operates in multiple countries or has plants with different schedules, use one calendar per context:
const CALENDARS = {
CL: "plant-santiago",
CO: "plant-bogota",
PE: "plant-lima",
};
async function calculateDeadline(country, date, days) {
const cal = CALENDARS[country];
const params = cal ? &calendar=${cal} : "";
const url = ${API}/${country}/business-days/add?date=${date}&days=${days}${params};
const { data } = await fetch(url, { headers: HEADERS }).then(r => r.json());
return data.result_date;
}
Each plant has its own closure schedule. The calculation logic stays the same — only the calendar slug changes.
Keeping calendars up to date
At the start of each year, or whenever closures are confirmed, an upsert script is enough. Duplicates are ignored, so running it multiple times is safe:
curl -X POST https://api.feriados.io/v1/calendars/plant-north/dates \
-H "Authorization: Bearer frd_your_key" \
-H "Content-Type: application/json" \
-d '{
"dates": [
{ "date": "2027-07-14", "name": "Annual maintenance 2027" },
{ "date": "2027-12-26", "name": "Year-end closure 2027" },
{ "date": "2027-12-27", "name": "Year-end closure 2027" }
]
}'
curl -X DELETE https://api.feriados.io/v1/calendars/plant-north/dates/2027-07-14 \ -H "Authorization: Bearer frd_your_key"
No deploys, no environment variable updates, no cross-team coordination.
Plan limits
Custom calendars by plan
The ?calendar=slug overlay works on all five business day endpoints
Custom calendars are available from the Starter plan
The Free plan includes 1 calendar with up to 10 future dates to test the feature.
Get your free API key →