Opening hours API
Fetch resolved opening hours and upcoming public holidays for a website
Last updated on
Use the resolved opening hours API if you show seasonal hours (for example Summer / Winter) or upcoming public holidays on a public site. Tillor already applies exceptions, seasonal periods and the regular weekly schedule, and adds statutory public holidays in the requested range. You do not have to implement that again.
Permission: organization:settings:read or organization:settings:write
Keep the API key server-side
Only call Tillor from a server (Next.js Route Handler, Server Component or backend). Put the key in environment variables. Never send it to the browser.
Authentication: see HTTP API. Try the endpoint in the API reference or via OpenAPI (tag organization-settings).
Endpoint
GET https://tillor.eu/api/orgs/org_abc123/organization-settings/opening-hoursHeaders:
x-api-key: tkn_xxxReplace org_abc123 with your organisation ID.
Query parameters
Use either date or from/to. Do not combine them.
| Parameter | Meaning |
|---|---|
| (none) | Today in the organisation's timezone |
date | A single calendar day (YYYY-MM-DD) |
from | Start day (inclusive). Without to: only that day |
to | End day (inclusive). Requires from. Maximum 366 days |
languageIsoCode | Language for public holiday names (NL, FR, …). Default: locale of the organisation |
Response
Each item is one calendar day, already resolved:
| Field | Meaning |
|---|---|
timeZone | IANA timezone of the organisation |
from / to | Requested range |
holidays | Statutory public holidays that overlap with from–to |
items[].date | Calendar day YYYY-MM-DD |
items[].weekday | monday … sunday |
items[].source | exception, period or regular |
items[].periodLabel | Only with source: "period": the label from Tillor, for example "Zomer" or "Winter" |
items[].holiday | Only if that day is a statutory public holiday: { id, name, startDate, endDate } |
items[].blocks | Time slots in minutes from midnight |
items[].closed | true if blocks is empty |
Priority: exception (public holiday or custom range) → last matching seasonal period → regular weekly schedule.
Season labels come from Settings > Opening hours. Periods may run across the turn of the year (for example 1 November to 31 March). Days outside every period fall back to the regular weekly schedule (source: "regular", no periodLabel).
Minutes to clock time
startMinutes / endMinutes are minutes from 00:00.
| Minutes | Time |
|---|---|
| 540 | 09:00 |
| 600 | 10:00 |
| 720 | 12:00 |
| 780 | 13:00 |
| 1020 | 17:00 |
| 1140 | 19:00 |
function formatMinutes(minutes: number): string {
const hour = Math.floor(minutes / 60);
const minute = minutes % 60;
return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
}Examples
A day in a seasonal period
curl -X GET "https://tillor.eu/api/orgs/org_abc123/organization-settings/opening-hours?date=2026-07-15" \
-H "x-api-key: tkn_xxx"{
"timeZone": "Europe/Brussels",
"from": "2026-07-15",
"to": "2026-07-15",
"holidays": [],
"items": [
{
"date": "2026-07-15",
"weekday": "wednesday",
"source": "period",
"periodLabel": "Zomer",
"blocks": [{ "startMinutes": 540, "endMinutes": 720 }],
"closed": false
}
]
}09:00–12:00, summer hours.
A day outside seasonal periods
curl -X GET "https://tillor.eu/api/orgs/org_abc123/organization-settings/opening-hours?date=2026-01-15" \
-H "x-api-key: tkn_xxx"{
"timeZone": "Europe/Brussels",
"from": "2026-01-15",
"to": "2026-01-15",
"holidays": [],
"items": [
{
"date": "2026-01-15",
"weekday": "thursday",
"source": "regular",
"blocks": [
{ "startMinutes": 540, "endMinutes": 720 },
{ "startMinutes": 780, "endMinutes": 1020 }
],
"closed": false
}
]
}09:00–12:00 and 13:00–17:00.
A range across a period boundary
curl -X GET "https://tillor.eu/api/orgs/org_abc123/organization-settings/opening-hours?from=2026-03-30&to=2026-04-02" \
-H "x-api-key: tkn_xxx"| Date | source | periodLabel |
|---|---|---|
| 2026-03-30 | regular | - |
| 2026-03-31 | regular | - |
| 2026-04-01 | period | Zomer |
| 2026-04-02 | period | Zomer |
Upcoming public holidays
Statutory public holidays are in the same response: holidays for a list (name + dates), items[].holiday if that calendar day is a public holiday. Hours remain the resolved schedule; a public holiday without a filled-in exception in Tillor follows the seasonal or weekly schedule (source is then not "exception").
curl -X GET "https://tillor.eu/api/orgs/org_abc123/organization-settings/opening-hours?from=2026-08-27&to=2026-12-31&languageIsoCode=NL" \
-H "x-api-key: tkn_xxx"{
"timeZone": "Europe/Brussels",
"from": "2026-08-27",
"to": "2026-12-31",
"holidays": [
{
"id": "0fad0035-6b77-404a-953d-c66dac014483",
"name": "Allerheiligen",
"startDate": "2026-11-01",
"endDate": "2026-11-01"
},
{
"id": "d90e5f53-bc92-4860-bdae-f7e75935848d",
"name": "Kerstmis",
"startDate": "2026-12-25",
"endDate": "2026-12-25"
}
],
"items": [
{
"date": "2026-11-01",
"weekday": "sunday",
"source": "regular",
"holiday": {
"id": "0fad0035-6b77-404a-953d-c66dac014483",
"name": "Allerheiligen",
"startDate": "2026-11-01",
"endDate": "2026-11-01"
},
"blocks": [],
"closed": true
}
]
}The items example above is shortened; the real response contains every day in the range. School holidays are not in this endpoint; they remain GET /holidays?kind=school.
Next.js (server)
For a week widget or “open now” on a public site:
const TILLOR_API_URL = "https://tillor.eu";
const ORGANIZATION_ID = process.env.TILLOR_ORG_ID ?? "";
type OpeningHoursBlock = {
startMinutes: number;
endMinutes: number;
};
type OpeningHoursHoliday = {
id: string;
name: string;
startDate: string;
endDate: string;
};
type OpeningHoursItem = {
date: string;
weekday: string;
source: "exception" | "period" | "regular";
periodLabel?: string;
holiday?: OpeningHoursHoliday;
blocks: OpeningHoursBlock[];
closed: boolean;
};
type OpeningHoursResponse = {
timeZone: string;
from: string;
to: string;
holidays: OpeningHoursHoliday[];
items: OpeningHoursItem[];
};
export async function getOpeningHours(
from: string,
to: string,
): Promise<OpeningHoursResponse> {
const url = new URL(
`${TILLOR_API_URL}/api/orgs/${ORGANIZATION_ID}/organization-settings/opening-hours`,
);
url.searchParams.set("from", from);
url.searchParams.set("to", to);
url.searchParams.set("languageIsoCode", "NL");
const response = await fetch(url, {
headers: {
"x-api-key": process.env.TILLOR_API_KEY ?? "",
},
next: { tags: ["opening-hours"], revalidate: 3600 },
});
if (!response.ok) {
throw new Error(`Tillor opening hours ${response.status}`);
}
return response.json() as Promise<OpeningHoursResponse>;
}holidays is the list for an “upcoming public holidays” block. items[].holiday is handy in a week calendar. items[].periodLabel is season text (for example "Zomer" or "Winter"). A one-hour cache is usually enough.
Invalidating the cache (webhook)
On every change to organisation settings, Tillor sends organizationSettings:updated (webhook and SSE). Subscribe to it and fetch the opening hours again. The payload is deliberately small (no printer IPs or the full schedule):
{
"event": "organizationSettings:updated",
"data": {
"openingHoursChanged": true
},
"timestamp": 1787829600000
}openingHoursChanged is true if the opening hours schedule, the timezone or the default country changes - that is what affects GET .../opening-hours. With false you can leave the opening hours cache in place.
Next.js Route Handler example:
import { revalidateTag } from "next/cache";
export async function POST(request: Request) {
const body = (await request.json()) as {
event?: string;
data?: { openingHoursChanged?: boolean };
};
if (body.event === "organizationSettings:updated" && body.data?.openingHoursChanged) {
await revalidateTag("opening-hours");
}
return new Response(null, { status: 204 });
}Creating a webhook: see Webhooks. Event key: organizationSettings:updated (permission organization:settings:read or organization:settings:write).
Full schedule (optional)
Only needed if you want to show the season calendar yourself (labels + fromMonth/untilDay), without resolving per day:
GET https://tillor.eu/api/orgs/org_abc123/organization-settingsRead openingHoursSchedule.periods (labels such as "Zomer") and openingHoursSchedule.weekdays (regular schedule). For the website itself, GET .../opening-hours remains the simplest source.
MCP
In Cursor or Claude: endpoint organizationSettings_listOpeningHours (category organizationSettings), arguments organizationId, optionally date or from/to, optionally languageIsoCode.
Related
- HTTP API - authentication and headers
- HTTP examples - general cURL examples
- Webhooks -
organizationSettings:updatedfor cache revalidation - Organization settings - setting up opening hours in Tillor