Events lifecycle¶
Events are scheduled, live learning — workshops, all-hands, office hours. Unlike on-demand content, people don't get assigned to an event; they enroll. This guide walks the full arc: define a reusable series, schedule a concrete event, enroll attendees, and check them in on the day.
How the pieces fit¶
graph LR
ES[Event series<br/>template] -->|defaults flow into| EV[Event<br/>scheduled occurrence]
EV -->|has one or more| TS[Timeslots]
U[User] -->|enrolls in| EV
EN[Enrollment] -->|check in on the day| CI[checkin_datetime]
- An event series is an optional template carrying default settings — location, capacity, in-person/online modes. Create it once, reuse it for every occurrence of a recurring event.
- An event is a concrete scheduled occurrence with one or more timeslots (the actual sessions). An event can belong to a series (inheriting its defaults) or stand alone.
- An enrollment links a user to an event, with an attendance method and check-in state.
1. (Optional) Create an event series¶
If this is a one-off, skip to step 2. For anything recurring, define the template first so every event shares the same defaults:
curl -X POST https://acme.plusplus.app/api/v2/event-series/ \
-H "Authorization: Bearer pp_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Weekly Engineering Sync",
"duration": 60,
"location_id": 42,
"is_local": true,
"is_online": true,
"enrollment_limit": 50,
"online_enrollment_limit": 200
}'
{
"public_id": "6ba7b810-…",
"name": "Weekly Engineering Sync",
"is_local": true,
"is_online": true,
"enrollment_limit": 50,
"online_enrollment_limit": 200,
"location": { "id": 42, "name": "San Francisco HQ", "timezone": "America/Los_Angeles" }
}
location_id is the numeric id of a location (the one place in the API you reference something by a numeric id rather than a public_id). Capacity is per occurrence: enrollment_limit / wait_list_limit govern in-person seats, and the online_* variants govern online seats. 0 means unlimited.
2. Schedule an event¶
An event needs at least one timeslot. Link it to the series with event_series_id (or omit it for a standalone event):
curl -X POST https://acme.plusplus.app/api/v2/events/ \
-H "Authorization: Bearer pp_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Weekly Engineering Sync — Mar 18",
"event_series_id": "6ba7b810-…",
"timeslots": [
{
"starts_at": "2026-03-18T17:00:00Z",
"duration": "PT1H",
"rooms_info": "Room 3A, Building 2",
"watch_link": "https://meet.acme.com/eng-sync"
}
]
}'
{
"public_id": "c8a7f1e2-…",
"name": "Weekly Engineering Sync — Mar 18",
"starts_at": "2026-03-18T17:00:00Z",
"ends_at": "2026-03-18T18:00:00Z",
"event_series": { "public_id": "6ba7b810-…", "name": "Weekly Engineering Sync" },
"timeslots": [
{
"id": 42,
"starts_at": "2026-03-18T17:00:00Z",
"ends_at": "2026-03-18T18:00:00Z",
"duration": "PT1H",
"rooms_info": "Room 3A, Building 2",
"watch_link": "https://meet.acme.com/eng-sync"
}
]
}
A few timeslot details:
durationis an ISO-8601 duration string:PT1H,PT1H30M,PT90M.starts_at— send the local wall-clock time the session begins, labeled as UTC (Z). The event's location supplies the real timezone. See the events module conventions in the API reference for the full rationale.- The event's top-level
starts_at/ends_atare computed from its timeslots — you don't set them directly.
To list the events generated from a series, filter by it:
curl "https://acme.plusplus.app/api/v2/events/?event_series_id=6ba7b810-…&ordering=created" \
-H "Authorization: Bearer pp_your_token_here"
ordering supports name, created, and modified (prefix - for descending). To present events chronologically, sort by each event's computed starts_at client-side.
3. Enroll attendees¶
Enroll a user by referencing the event and an attendance method:
curl -X POST https://acme.plusplus.app/api/v2/enrollments/ \
-H "Authorization: Bearer pp_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"user_id": "8f3a…",
"event_id": "c8a7f1e2-…",
"attendance_method": "in_person",
"accept_waitlist": true
}'
{
"public_id": "d4e5f6a7-…",
"user": { "public_id": "8f3a…", "name": "Jane Doe", "email": "jane.doe@acme.com" },
"event": { "public_id": "c8a7f1e2-…", "name": "Weekly Engineering Sync — Mar 18" },
"status": "enrolled",
"attendance_method": "in_person",
"checkin_datetime": null
}
Behavior worth knowing:
attendance_methodisin_personoronline; capacity is tracked separately for each.accept_waitlist— when the chosen method is full, set thistrueto take a waitlist spot (status: "waitlisted") instead of getting a409.- Create-or-get:
201for a new enrollment,200if an identical active enrollment already exists. A409means the method is full (and you didn't accept the waitlist), or an active enrollment exists with a different method.
To enroll a whole group at once, use the bulk endpoint — see Bulk operations & async jobs.
4. Check in on the day¶
When the attendee shows up, record it:
curl -X POST https://acme.plusplus.app/api/v2/enrollments/d4e5f6a7-…/checkin/ \
-H "Authorization: Bearer pp_your_token_here"
Check-in is idempotent — checking in someone already checked in returns 200, not an error — so it's safe to retry or wire to a "scan badge" button. It returns 409 only if the enrollment was dropped.
5. Handle drops and cancellations¶
curl -X POST https://acme.plusplus.app/api/v2/enrollments/d4e5f6a7-…/drop/ \
-H "Authorization: Bearer pp_your_token_here"
drop sets status: "dropped". It returns 409 if the attendee has already been checked in — you can't un-attend someone. When a waitlisted spot opens up because of a drop, promote a waitlisted enrollment as your business rules require.
6. After the event: recordings¶
Captured the session? Attach the recording to the series so it plays back for everyone, via the Mux upload flow. That's its own walkthrough — see Uploading recordings.
Common pitfalls¶
- Trying to assign an event. Events use enrollments, not assignments. There's no assignment endpoint for events.
- Setting
starts_aton the event. It's derived from timeslots — schedule the timeslot and the event's start/end follow. - Forgetting a timeslot. An event create requires at least one timeslot; an event with no session can't be scheduled.
- Mismatched online/in-person limits. In-person and online seats are independent. A full room doesn't block online enrollment, and vice versa.
- Deleting an event referenced by a track.
DELETEreturns409if a track references the event. Remove it from the track first.