Skip to content

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:

  • duration is 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_at are 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_method is in_person or online; capacity is tracked separately for each.
  • accept_waitlist — when the chosen method is full, set this true to take a waitlist spot (status: "waitlisted") instead of getting a 409.
  • Create-or-get: 201 for a new enrollment, 200 if an identical active enrollment already exists. A 409 means 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_at on 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. DELETE returns 409 if a track references the event. Remove it from the track first.