🧭 Overview
- Single endpoint:
GET /api/events - Returns a unified JSON payload covering today’s events, the week ahead, announcements, pub hours, and the on-duty pub tender
- Cached at the Cloudflare edge for 2 minutes
- Falls back to placeholder data if Notion credentials are not configured
Sources: worker.js (handleEventsApi, PLACEHOLDER block)
📡 GET /api/events
Response shape
{
"todayEvents": [
/* Event[] */
],
"weekEvents": [
/* WeekEvent[] */
],
"announcements": [
/* Announcement[] */
],
"pubHoursWeek": [
/* PubHoursDay[] */
],
"pubTender": {
/* PubTender */
},
"lastUpdated": "2026-03-17T23:45:00.000Z",
"source": "notion"
}source is "notion" when live data is returned and "placeholder" when the worker is serving fallback data.
Event object (todayEvents)
Today’s pub events, sorted by start time. This array powers the TV display’s Happening at the Pub panel.
Eligibility rules
An event is included in todayEvents only if all of the following are true:
Include on Pub TVis checkedLocationcontains"pub"(case-insensitive)Event Startlands on the worker’stodayISOwhen interpreted inAmerica/New_York
To make that last rule reliable, the worker intentionally queries a wider Notion window:
Event Start >= todayISOEvent Start < todayISO + 2 days
It then narrows the results with a local-date comparison in Eastern time. This avoids losing late-evening events whose timestamp spills into the next UTC day.
Sources: worker.js (fetchFromNotion, isAtPub, isOnLocalDate)
What counts as “today”
The worker uses the normal calendar day in America/New_York. Midnight starts the new day.
That means:
- At
12:00 AMEastern,todayEventsswitches to the new calendar date - Same-day events later that day are immediately eligible for
todayEvents weekEventsalways starts at calendar tomorrow, so same-day events are not duplicated there
Source: worker.js (fetchFromNotion)
{
"title": "Trivia Night",
"start": "8:30 PM",
"end": "10:00 PM",
"status": "Later",
"startIso": "2026-01-18T01:30:00.000Z",
"endIso": "2026-01-18T03:00:00.000Z",
"rcTag": "rc-direct"
}| Field | Type | Notes |
|---|---|---|
title | string | Event name from Notion |
start | string | Formatted start time (e.g. "8:30 PM") |
end | string | Formatted end time; derived from Event End, Duration (hrs), or a 1-hour fallback |
status | string | Computed at request time: "Now", "Next", "Later", or "Done" |
startIso | string | UTC ISO 8601 start timestamp (used for client-side re-evaluation) |
endIso | string | UTC ISO 8601 end timestamp |
rcTag | string | null | "rc-direct", "rc-affiliate", or omitted |
Status freshness
statusis computed by the worker at request time. The TV display client re-evaluates status locally every minute usingstartIso/endIsoso the badge stays accurate between 2-minute API refreshes.
Sources: worker.js (transformPage, deriveStatus)
WeekEvent object (weekEvents)
Upcoming events for the next 7 calendar days, sorted by start time. This array powers the TV display’s Coming Up at the Pub panel.
Eligibility rules
An event is included in weekEvents only if all of the following are true:
Include on Pub TVis checkedLocationcontains"pub"(case-insensitive)Event Start >= tomorrowISOEvent Start < todayISO + 8 days
tomorrowISO is the next calendar date in America/New_York.
Normal behavior:
- Same-day events are not duplicated into
weekEvents weekEventsstarts with calendar tomorrow, then continues through calendar day +7- The TV client removes any row whose live status has already become
Done
Sources: worker.js (fetchFromNotion, isAtPub, transformWeekPage), public/tv/script.js (renderWeekEvents)
{
"title": "Open Mic Night",
"date": "Wed, Jan 21",
"start": "9:00 PM",
"end": "11:00 PM",
"startIso": "2026-01-22T02:00:00.000Z",
"endIso": "2026-01-22T04:00:00.000Z",
"rcTag": null
}| Field | Type | Notes |
|---|---|---|
title | string | Event name |
date | string | Formatted date label (e.g. "Wed, Jan 21") |
start | string | Formatted start time |
end | string | Formatted end time; derived from Event End, Duration (hrs), or a 1-hour fallback |
startIso | string | UTC ISO 8601 start timestamp (used for past-event filtering on client) |
endIso | string | UTC ISO 8601 end timestamp |
rcTag | string | null | RC classification tag |
Sources: worker.js (transformWeekPage)
Announcement object (announcements)
Rows are included in announcements only when Include on Pub TV is checked in the Announcements database.
The worker maps announcement text as:
Contentrich text, if present- otherwise
Name
{
"text": "The pub will close early this Friday at 10 PM."
}| Field | Type | Notes |
|---|---|---|
text | string | Announcement copy from Content or fallback Name |
Sources: worker.js (fetchAnnouncementsFromNotion)
Priority styling is present in the client, but the worker currently emits announcement text only. Every announcement renders in the normal style unless the API shape is extended.
PubHoursDay object (pubHoursWeek)
Array of 7 items, one per day of the current week, indexed 0–6 from Sunday through Saturday.
{
"date": "2026-03-17",
"open": true,
"hours": "9:00 PM – 11:40 PM"
}| Field | Type | Notes |
|---|---|---|
date | string | ISO date (YYYY-MM-DD) in Eastern time |
open | boolean | false if pub is closed that day |
hours | string | null | Formatted operational timeframe; null if closed |
Sources: worker.js (fetchPubHoursWeek)
PubTender object (pubTender)
{
"name": "Alex & Jordan",
"hours": "9:00 PM – 11:40 PM",
"note": "On duty now",
"noteState": "active",
"pageUrl": "https://notion.so/..."
}| Field | Type | Notes |
|---|---|---|
name | string | First name(s) of on-duty tender(s), joined with " & " |
hours | string | Operational timeframe from the Shifts DB |
note | string | Human-readable shift state label |
noteState | string | Machine-readable state (see table below) |
pageUrl | string | Notion page URL for QR code; empty if unavailable |
noteState values
| Value | Meaning |
|---|---|
upcoming | Shift starts later today |
setup | Within full shift window, before operational hours begin |
active | Within operational hours |
last-call | Within lastCallMinutes minutes of operational end |
closed | After operational hours but still within the full shift (cleaning) |
inactive | No active shift found for today |
Sources: worker.js (fetchPubTenderFromNotion, deriveShiftNote)
❓ Open Questions
Fallback vs live source
When
NOTION_SHIFTS_DATABASE_IDorNOTION_ANNOUNCEMENTS_DATABASE_IDare not set, the corresponding keys in the response still appear but contain empty arrays / placeholder values. Callers should handle this gracefully.