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/ |
completed → in_progress |
it isn't completed |
| Undo exempt | POST /assignments/{id}/undo-exempt/ |
exempted → in_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 onstaterather than retrying. - Polling for progress.
progressandstateupdate 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.