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:
// The classic approach
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:
# Is July 15th a working day at Plant North?
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;
}
// Skip payroll processing during plant shutdowns
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;
}
// Order placed July 14 — 3 business days delivery, skipping July 15 maintenance
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;
}
// 5-business-day SLA opened July 13, respecting plant closure on July 15
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" }
]
}'
If a date changes, delete the old one and add the corrected date:
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
| Plan | Calendars | Future dates per calendar |
|---|---|---|
| Free | 1 | 10 |
| Starter | 3 | Unlimited |
| Team | 10 | Unlimited |
| Business | Unlimited | Unlimited |
The ?calendar=slug overlay works on all five business day endpoints: is-business-day, business-days/add, business-days/subtract, business-days/between, and last-business-day.
Custom calendars are available from the Starter plan. The Free plan includes 1 calendar with up to 10 future dates to test the feature. See plans → · Get your free API key →
See also: Custom Calendars documentation → · SLA business days in LATAM → · Business Days API guide →