Skip to content

Assignments & completion tracking

An assignment links a user to a content item and tracks how far they've gotten. This guide covers assigning content, driving an assignment through its lifecycle, and reading completion back out as analytics.

The assignment lifecycle

stateDiagram-v2
    [*] --> not_started: create
    not_started --> in_progress: learner engages
    in_progress --> completed: complete / auto-complete
    not_started --> exempted: exempt
    in_progress --> exempted: exempt
    not_started --> dropped: drop
    in_progress --> dropped: drop
    completed --> in_progress: undo-complete
    exempted --> in_progress: undo-exempt

Every assignment has a state: not_started, in_progress, completed, dropped, or exempted. Learners normally drive the state themselves by engaging with content; the API lets you set it explicitly for integrations, corrections, and administrative workflows.

1. Assign content to a user

curl -X POST https://acme.plusplus.app/api/v2/assignments/ \
  -H "Authorization: Bearer pp_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "8f3a1b2c-…",
    "content_item_id": "a1b2c3d4-…",
    "due_date": "2026-12-31"
  }'
{
  "public_id": "f1e2d3c4-…",
  "user": { "public_id": "8f3a1b2c-…", "name": "Jane Doe", "email": "jane.doe@acme.com" },
  "content_item": { "public_id": "a1b2c3d4-…", "name": "Python Fundamentals", "content_type": "article" },
  "state": "not_started",
  "progress": 0.0,
  "due_date": "2026-12-31",
  "is_overdue": false,
  "created": "2026-06-02T09:30:00Z"
}

createAssignment is create-or-get: it returns 201 for a brand-new assignment and 200 if the user is already assigned to that content item. That makes it safe to retry — you won't create duplicates. due_date (a YYYY-MM-DD date) and expires_at (a datetime) are optional.

Assigning to many users at once

To assign one content item to a whole cohort — or many content items to one user — use the bulk endpoint instead of looping. See Bulk operations & async jobs. Assigning a track automatically creates assignments for each of its items; see Building learning paths.

2. Drive the state

Each transition is its own action endpoint. None take a request body (except drop, which optionally takes a reason), and each returns the updated assignment.

Action Endpoint Effect Rejects with 409 when…
Complete POST /assignments/{id}/complete/ completed, sets completed_datetime the assignment is dropped
Drop POST /assignments/{id}/drop/ dropped, sets dropped_at the assignment is completed
Exempt POST /assignments/{id}/exempt/ exempted, sets exempted_at it isn't outstanding (already completed/dropped/exempted)
Undo complete POST /assignments/{id}/undo-complete/ completedin_progress it isn't completed
Undo exempt POST /assignments/{id}/undo-exempt/ exemptedin_progress it isn't exempted

Mark an assignment complete:

curl -X POST https://acme.plusplus.app/api/v2/assignments/f1e2d3c4-…/complete/ \
  -H "Authorization: Bearer pp_your_token_here"
{
  "public_id": "f1e2d3c4-…",
  "state": "completed",
  "progress": 1.0,
  "completed_datetime": "2026-06-10T14:05:00Z",
  "is_overdue": false
}

Drop with a reason:

curl -X POST https://acme.plusplus.app/api/v2/assignments/f1e2d3c4-…/drop/ \
  -H "Authorization: Bearer pp_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "No longer relevant to role" }'

The 409 guards prevent illegal transitions — you can't drop something already completed, for instance. Treat a 409 as "the assignment isn't in a state where this makes sense," re-fetch it, and decide what to do.

Exempt vs. drop

Both take an assignment out of a learner's queue, but they mean different things for reporting:

  • Exempt — the learner didn't need to do this (already certified, not applicable to their role). Exempted assignments are excluded from "outstanding" but counted as a distinct bucket.
  • Drop — the assignment was removed (mistaken assignment, content retired). Dropped assignments fall out of completion math entirely.

3. Adjust due dates

Update mutable fields with PATCH. Pass null to clear a date:

curl -X PATCH https://acme.plusplus.app/api/v2/assignments/f1e2d3c4-…/ \
  -H "Authorization: Bearer pp_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{ "due_date": "2027-01-31" }'

4. Find assignments

The list endpoint filters on the dimensions you'll actually report against:

# Everything Jane still owes
curl "https://acme.plusplus.app/api/v2/assignments/?user=8f3a1b2c-…&state=in_progress" \
  -H "Authorization: Bearer pp_your_token_here"

# Overdue assignments for a course, across all learners
curl "https://acme.plusplus.app/api/v2/assignments/?content_item=a1b2c3d4-…&is_overdue=true" \
  -H "Authorization: Bearer pp_your_token_here"

Filterable fields include user, content_item, state, content_type, is_overdue, and due_date__gte / due_date__lte. See Filtering & sorting.

5. Read completion as analytics

When you want the aggregate picture rather than individual rows, use Insights — pre-computed, read-only roll-ups. They accept the same filters (content_item_id, user_id, group_id, date range) so you can scope to a course, a person, or a team.

curl "https://acme.plusplus.app/api/v2/insights/assignments/completion/?content_item_id=a1b2c3d4-…" \
  -H "Authorization: Bearer pp_your_token_here"
{
  "total": 150,
  "completed":   { "count": 87, "percentage": 58 },
  "in_progress": { "count": 42, "percentage": 28 },
  "not_started": { "count": 15, "percentage": 10 },
  "dropped":     { "count": 5,  "percentage": 3 },
  "exempted":    { "count": 1,  "percentage": 1 }
}

The insights surface:

Endpoint Answers
/insights/assignments/completion/ How are assignments distributed across the five states?
/insights/assignments/overdue/ How many are overdue, split by not_started vs in_progress?
/insights/assignments/feedback/ How many ratings and written reviews came back?
/insights/assignments/ratings/ What's the 1–5 star distribution and average?
/insights/assignments/pass-fail/ For graded content, what's the pass/fail split?

All five buckets are always present (even at count: 0), and percentages are whole integers — so you can render a chart without defensive null checks.

Common pitfalls

  • Looping to assign a cohort. Use bulk assignment; single POSTs will hit your rate limit and run slowly.
  • Fighting a 409. It means the transition is invalid for the current state (e.g. completing a dropped assignment). Re-fetch and branch on state rather than retrying.
  • Polling for progress. progress and state update as the learner engages with content — you don't drive them with repeated calls. To watch for completion at scale, prefer the completion insight over polling thousands of individual assignments.
  • Confusing assignments with enrollments. Events aren't assigned — they're enrolled. See Events lifecycle.