🧭 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 TV is checked
  • Location contains "pub" (case-insensitive)
  • Event Start lands on the worker’s todayISO when interpreted in America/New_York

To make that last rule reliable, the worker intentionally queries a wider Notion window:

  • Event Start >= todayISO
  • Event 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 AM Eastern, todayEvents switches to the new calendar date
  • Same-day events later that day are immediately eligible for todayEvents
  • weekEvents always 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"
}
FieldTypeNotes
titlestringEvent name from Notion
startstringFormatted start time (e.g. "8:30 PM")
endstringFormatted end time; derived from Event End, Duration (hrs), or a 1-hour fallback
statusstringComputed at request time: "Now", "Next", "Later", or "Done"
startIsostringUTC ISO 8601 start timestamp (used for client-side re-evaluation)
endIsostringUTC ISO 8601 end timestamp
rcTagstring | null"rc-direct", "rc-affiliate", or omitted

Status freshness

status is computed by the worker at request time. The TV display client re-evaluates status locally every minute using startIso/endIso so 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 TV is checked
  • Location contains "pub" (case-insensitive)
  • Event Start >= tomorrowISO
  • Event Start < todayISO + 8 days

tomorrowISO is the next calendar date in America/New_York.

Normal behavior:

  • Same-day events are not duplicated into weekEvents
  • weekEvents starts 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
}
FieldTypeNotes
titlestringEvent name
datestringFormatted date label (e.g. "Wed, Jan 21")
startstringFormatted start time
endstringFormatted end time; derived from Event End, Duration (hrs), or a 1-hour fallback
startIsostringUTC ISO 8601 start timestamp (used for past-event filtering on client)
endIsostringUTC ISO 8601 end timestamp
rcTagstring | nullRC 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:

  • Content rich text, if present
  • otherwise Name
{
  "text": "The pub will close early this Friday at 10 PM."
}
FieldTypeNotes
textstringAnnouncement 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 06 from Sunday through Saturday.

{
  "date": "2026-03-17",
  "open": true,
  "hours": "9:00 PM – 11:40 PM"
}
FieldTypeNotes
datestringISO date (YYYY-MM-DD) in Eastern time
openbooleanfalse if pub is closed that day
hoursstring | nullFormatted 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/..."
}
FieldTypeNotes
namestringFirst name(s) of on-duty tender(s), joined with " & "
hoursstringOperational timeframe from the Shifts DB
notestringHuman-readable shift state label
noteStatestringMachine-readable state (see table below)
pageUrlstringNotion page URL for QR code; empty if unavailable

noteState values

ValueMeaning
upcomingShift starts later today
setupWithin full shift window, before operational hours begin
activeWithin operational hours
last-callWithin lastCallMinutes minutes of operational end
closedAfter operational hours but still within the full shift (cleaning)
inactiveNo active shift found for today

Sources: worker.js (fetchPubTenderFromNotion, deriveShiftNote)


❓ Open Questions

Fallback vs live source

When NOTION_SHIFTS_DATABASE_ID or NOTION_ANNOUNCEMENTS_DATABASE_ID are not set, the corresponding keys in the response still appear but contain empty arrays / placeholder values. Callers should handle this gracefully.