Skip to Content
Examples

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-hours

Headers:

x-api-key: tkn_xxx

Replace org_abc123 with your organisation ID.

Query parameters

Use either date or from/to. Do not combine them.

ParameterMeaning
(none)Today in the organisation's timezone
dateA single calendar day (YYYY-MM-DD)
fromStart day (inclusive). Without to: only that day
toEnd day (inclusive). Requires from. Maximum 366 days
languageIsoCodeLanguage for public holiday names (NL, FR, …). Default: locale of the organisation

Response

Each item is one calendar day, already resolved:

FieldMeaning
timeZoneIANA timezone of the organisation
from / toRequested range
holidaysStatutory public holidays that overlap with from–to
items[].dateCalendar day YYYY-MM-DD
items[].weekdaymonday … sunday
items[].sourceexception, period or regular
items[].periodLabelOnly with source: "period": the label from Tillor, for example "Zomer" or "Winter"
items[].holidayOnly if that day is a statutory public holiday: { id, name, startDate, endDate }
items[].blocksTime slots in minutes from midnight
items[].closedtrue 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.

MinutesTime
54009:00
60010:00
72012:00
78013:00
102017:00
114019: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"
DatesourceperiodLabel
2026-03-30regular-
2026-03-31regular-
2026-04-01periodZomer
2026-04-02periodZomer

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-settings

Read 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

Was this page helpful?

On this page