Skip to content

Scheduling track cohorts (Scheduled Tracks)

A scheduled track is a time-bound instance of a track, run for a specific cohort. Where a track is a reusable template — "Engineering Onboarding" — a scheduled track is one delivery of it: "Engineering Onboarding, Q2 2025", with real dates, a location, a capacity, and concrete event sessions in place of the template's event-series placeholders.

This guide creates a scheduled track from a template, picks the events for it, adjusts its schedule and items, and (optionally) links a Slack channel.

How it differs from a track

  • A scheduled track is created from a template track and keeps a read-only reference to it (original_track).
  • It snapshots the template's items at creation time. Editing the template afterward does not change an existing scheduled track.
  • It carries scheduling fields a plain track doesn't: starts_at, ends_at, location, is_online, is_local, and capacity.
  • Template tracks usually contain event-series placeholders ("Weekly Sync" as a type). A scheduled track replaces each placeholder with a concrete event — a specific session on a specific date — via the swap-events action.

1. Schedule a track

Turn a template track into a cohort instance with the schedule action on the source track:

curl -X POST https://acme.plusplus.app/api/v2/tracks/7a8b9c0d-…/schedule/ \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Engineering Onboarding (Q2 2025)",
    "starts_at": "2025-04-01T09:00:00Z",
    "ends_at": "2025-04-30T17:00:00Z",
    "location_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "is_online": true,
    "is_local": true,
    "capacity": 20
  }'
{
  "public_id": "f1e2d3c4-…",
  "name": "Engineering Onboarding (Q2 2025)",
  "content_type": "scheduled_track",
  "original_track": { "public_id": "7a8b9c0d-…", "name": "Engineering Onboarding" },
  "starts_at": "2025-04-01T09:00:00Z",
  "ends_at": "2025-04-30T17:00:00Z",
  "location": { "public_id": "6ba7b810-…", "name": "San Francisco HQ", "timezone": "America/Los_Angeles" },
  "capacity": 20,
  "is_capacity_full": false,
  "track_items_count": 6,
  "slack_channel": null
}

A few things to know:

  • Every field is optional. Omit name and it inherits the template's name; omit the scheduling fields and they stay empty. The new scheduled track still snapshots the template's items.
  • capacity is 0 for unlimited. When set, is_capacity_full flips to true once assignments reach it.
  • location_id is the location's public_id (the same id you see in an event series' location).
  • The template is untouched. Scheduling reads the template's items and copies them onto the new scheduled track.

2. Pick events for each event-series slot

Right after scheduling, any event-series placeholder in the track is still a type, not a real session. Use swap-events to choose the concrete event for each slot:

curl -X POST https://acme.plusplus.app/api/v2/scheduled-tracks/f1e2d3c4-…/swap-events/ \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "swapped_events": [
      {
        "old_event": "e1111111-…",
        "new_event": "e6666666-…",
        "enrollment_action": "move"
      }
    ]
  }'
  • old_event is the public_id of the slot you're filling — either the event-series placeholder currently in the track (first-time scheduling) or a concrete event already in it (re-pointing the cohort at a new date).
  • new_event is the public_id of the concrete event to drop into that slot.
  • enrollment_action decides what happens to people already enrolled on the old slot:

    • keep — leave their enrollments on the old event; don't enroll them in the new one.
    • move — migrate their enrollments to the new event, preserving each attendee's method (in person vs. online).

    When the slot is an event-series placeholder, nobody is enrolled on it yet, so the action decides about the cohort's assignees instead: move enrolls them in the new event (online when it offers both formats), and keep enrolls no one.

You can swap several slots in one call. The response is the updated scheduled track.

Running the next cohort

To stand up the next cohort, schedule the template again and swap in that cohort's event dates. The first cohort's scheduled track keeps its own events — the two cohorts are fully independent.

3. Change the cohort's schedule

Dates slip and rooms change. Update the scheduling fields — and any other track field, such as name or is_hidden — with a partial update:

curl -X PATCH https://acme.plusplus.app/api/v2/scheduled-tracks/f1e2d3c4-…/ \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -H 'If-Match: W/"a1b2c3"' \
  -d '{
    "starts_at": "2025-04-08T09:00:00Z",
    "ends_at": "2025-05-07T17:00:00Z",
    "location_id": null,
    "capacity": 25
  }'
  • Only the fields you send change. Send null to clear starts_at, ends_at or location_id.
  • If-Match is optional but recommended. Pass the ETag from your last read and the update is rejected with 412 if someone changed the cohort since.
  • The template is fixed. A scheduled track always points at the track it was scheduled from.
  • capacity counts every assignment, including dropped ones, so lowering it below the number of assignments makes is_capacity_full read true.

4. Adjust the cohort's items

A scheduled track has the same /track-items/ and /sections/ sub-resources as a track, and they behave the same way — see Building learning paths. For example, to add a welcome article at the top (on a cohort with sections, also pass the section_id it goes in):

curl -X POST https://acme.plusplus.app/api/v2/scheduled-tracks/f1e2d3c4-…/track-items/ \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{ "items": [{ "content_item_id": "a9b8c7d6-…", "position": 0 }] }'

What is specific to a scheduled track:

  • It keeps at least one item (422 empty_scheduled_track) and cannot contain another scheduled track (422 nested_scheduled_track).
  • Adding an item reaches people already assigned. Each assignee gets an assignment on the new item, and an enrollment when the item is an event. This happens in the background, shortly after the response.
  • Removing a concrete event marks its attendees as not going, without notifying them. To move attendees to a different session instead, swap the event (step 2) with enrollment_action: "move".
  • An event item's due date is always the event's end. An assignment_due_date sent for an event item is ignored.

5. Read progress and the cohort's events

A learner's progress on the cohort, measured over its required items:

curl "https://acme.plusplus.app/api/v2/scheduled-tracks/f1e2d3c4-…/progress/?user=0a1b2c3d-…" \
  -H "Authorization: Bearer your_token_here"

The cohort's concrete events, soonest first — the sessions attendees will actually go to. Event-series slots still waiting for an event (step 2) are not listed:

curl https://acme.plusplus.app/api/v2/scheduled-tracks/f1e2d3c4-…/events/ \
  -H "Authorization: Bearer your_token_here"

If your account has Slack connected, you can attach a channel that everyone assigned to the cohort is auto-invited to:

curl -X POST https://acme.plusplus.app/api/v2/scheduled-tracks/f1e2d3c4-…/slack-channel/ \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{ "name": "q2-2025-onboarding" }'
{ "id": "C0123456789", "name": "q2-2025-onboarding", "url": "https://acme.slack.com/archives/C0123456789" }

Posting a channel that doesn't exist creates it; posting an existing one links it. When a channel is already linked, posting a different name renames that channel in Slack for all its members. To remove the link without deleting the channel:

curl -X DELETE https://acme.plusplus.app/api/v2/scheduled-tracks/f1e2d3c4-…/slack-channel/ \
  -H "Authorization: Bearer your_token_here"

When Slack isn't configured for the account, the create call returns 400 slack_not_configured.

7. Read and list scheduled tracks

# One scheduled track
curl https://acme.plusplus.app/api/v2/scheduled-tracks/f1e2d3c4-…/ \
  -H "Authorization: Bearer your_token_here"

# All of them, newest first
curl "https://acme.plusplus.app/api/v2/scheduled-tracks/?ordering=-starts_at" \
  -H "Authorization: Bearer your_token_here"

The list supports search (by name), is_archived, and is_hidden filters, and ordering by name, created, modified, starts_at, or ends_at.

Assigning a scheduled track works exactly as it does for a track — see Building learning paths and Assignments & completion.

Common pitfalls

  • Scheduling a template whose Equivalent Content holds an option that can no longer be added. The cohort gets its own copy of each Equivalent Content step, so every option is checked again, the same way as when you edit that step. An archived option, or one you can no longer add, returns 400 validation_error. Remove the option from the template first.
  • Swapping an event that isn't in the track. old_event must reference a slot currently in this scheduled track, otherwise you get 400 event_not_in_track. List the track's items first if you're unsure which slots exist.
  • Pointing new_event at an event series. new_event must be a concrete event, not an event series — the series is the placeholder you're replacing.
  • Editing the template to change a running cohort. It won't. A scheduled track is a snapshot; change the cohort through its own update, items and swap-events endpoints.
  • Removing an event to reschedule it. Removing an event marks its attendees as not going. Swap the event for the new session with move instead, and attendees follow it.
  • Expecting keep to enroll people in the new event. keep deliberately leaves existing attendees on the old event, and on a placeholder it enrolls none of the cohort's assignees. Use move to bring them across.