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.

What the event inherits from its series

Everything you leave out of the create request is taken from the series, so the request above needs only a name and a timeslot to produce an event that matches one scheduled from the series in the web app:

Fields you can override by sending them:

Inherited Notes
body_markdown
tags Send "tags": [] to create the event without any.
location_id Send "location_id": null to create the event without a location.
enrollment_limit, wait_list_limit, online_enrollment_limit, online_wait_list_limit
is_local, is_online, is_hidden
external_link
The series' duration Applied to any timeslot that doesn't set its own duration.

Sending any of these overrides the series — including sending null, which is read as a decision rather than an omission.

Inherited with no way to override, because this endpoint has no field for them:

Inherited Notes
cover image The series is the only way to give an event a cover image.
Facilitators Organizer, co-organizers and presenters are copied onto the event. Adjust afterwards via the facilitators sub-resource.
Channel The event lands in the series' channel, which also governs who can see it.
Survey relationships Copied as new rows; their schedule is recomputed from the event's own timeslots.
external_link_description

Inheritance happens once, at creation: later edits to the series don't reach events already scheduled from it.

:::note Inheritance only applies to POST /events/. PATCH /events/{id}/ changes exactly the fields you send, so moving an existing event into a series does not backfill anything. :::

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.