Multi-day events (e.g., IL/MD CCW classes spanning 2 days, 18 hours total) are now fully supported. Each day can have different times and venues (classroom vs range). The data model was already in place -- this update adds admin UX for bulk creation and exposes structured multi-day data through the API.
Event (duration_type: "multi_day" | "single_day")
└── EventInstance (a "Session" -- e.g., "Mar 25-26 Session")
├── EventOccurrence (Day 1 -- classroom, 9am-5pm)
└── EventOccurrence (Day 2 -- range, 8am-12pm)
- An Event can have multiple Instances (sessions at different date ranges)
- Each Instance has multiple Occurrences (the individual days)
- Each Occurrence has:
starts_at,ends_at,day_label,venue_id - Registration is per-Instance (registering for one session covers all its days)
New fields are only returned when the client sends the x-supports-multi-day request header. This follows the same pattern as x-supports-native-checkout.
Without the header (existing clients):
{
"id": 1,
"title": "IL CCW 16-Hour Course",
"duration_type": "multi_day",
"instances": [
{
"id": 10,
"label": "Mar 25 - Mar 26, 2026",
"occurrences": [
{ "id": 100, "starts_at": "...", "ends_at": "...", "day_label": "Day 1 - Classroom" },
{ "id": 101, "starts_at": "...", "ends_at": "...", "day_label": "Day 2 - Range" }
]
}
]
}The duration_type and day_label fields were already present in the API. Existing clients already receive and can ignore these fields. No existing fields were changed or removed.
With x-supports-multi-day header (updated clients):
{
"id": 1,
"title": "IL CCW 16-Hour Course",
"duration_type": "multi_day",
"is_multi_day": true,
"total_days": 2,
"attendance_required": true,
"multi_day_notice": "All scheduled days must be attended to complete this training.",
"instances": [
{
"id": 10,
"label": "Mar 25 - Mar 26, 2026",
"day_count": 2,
"date_range": {
"starts_at": "2026-03-25T14:00:00Z",
"ends_at": "2026-03-26T17:00:00Z"
},
"occurrences": [
{ "id": 100, "starts_at": "...", "ends_at": "...", "day_label": "Day 1 - Classroom", "venue": { ... } },
{ "id": 101, "starts_at": "...", "ends_at": "...", "day_label": "Day 2 - Range", "venue": { ... } }
]
}
]
}- Old app versions continue working identically
- No silent JSON decode failures (no new required fields on existing models)
- The
duration_typefield was already in the response, so old clients that display it will show "multi_day" as-is - Instance labels now auto-generate as date ranges (e.g., "Mar 25 - Mar 26, 2026") which old clients will display naturally
In the API client, add the header to event detail requests:
// In your API request builder
request.addValue("true", forHTTPHeaderField: "x-supports-multi-day")Add the new optional fields to the event detail model:
struct EventDetail: Codable {
// ... existing fields ...
// New multi-day fields (optional -- only present with header)
let isMultiDay: Bool?
let totalDays: Int?
let attendanceRequired: Bool?
let multiDayNotice: String?
enum CodingKeys: String, CodingKey {
// ... existing keys ...
case isMultiDay = "is_multi_day"
case totalDays = "total_days"
case attendanceRequired = "attendance_required"
case multiDayNotice = "multi_day_notice"
}
}struct EventInstance: Codable {
// ... existing fields ...
// New multi-day fields (optional)
let dayCount: Int?
let dateRange: DateRange?
struct DateRange: Codable {
let startsAt: Date
let endsAt: Date
enum CodingKeys: String, CodingKey {
case startsAt = "starts_at"
case endsAt = "ends_at"
}
}
enum CodingKeys: String, CodingKey {
// ... existing keys ...
case dayCount = "day_count"
case dateRange = "date_range"
}
}- When
isMultiDay == true, show a banner: "This is a multi-day event. All scheduled days must be attended." - Show the
multiDayNoticestring if present
- For multi-day instances, show all occurrences under the instance card:
┌─────────────────────────────────────┐ │ ○ Mar 25 - Mar 26, 2026 (2 days) │ │ Day 1 - Classroom │ │ Wed, Mar 25 · 9:00 AM - 5:00 PM │ │ Main Training Center │ │ │ │ Day 2 - Range │ │ Thu, Mar 26 · 8:00 AM - 12:00 PM │ │ Outdoor Range │ └─────────────────────────────────────┘
- Show
day_labelbefore each occurrence date when present - Group occurrences by instance with the instance label as section header
- All new fields are optional. The app must handle
nilgracefully. - Registration flow is unchanged. Users still register for an Instance. The multi-day display is purely informational -- registering for a session covers all its days.
- No new API endpoints. Everything goes through the existing
GET /api/v1/events/:slugendpoint. - The
duration_typefield already exists in the response. You can useevent.durationType == "multi_day"as a local check even without the new header, but the additional structured fields (dayCount,dateRange, etc.) require the header.
- Phase 1 (done): Server-side support deployed. Old clients unaffected.
- Phase 2: Add
x-supports-multi-dayheader and new model fields to the iOS app. All new fields are optional, so this is backward-compatible with older server versions (fields will just be nil). - Phase 3: Add multi-day UI enhancements to event detail and registration screens.
- Phase 4: Ship app update. No server changes needed.