Enrollments, approval requests and per-learner progress.
List all enrollments
GET/learners
Enrollments visible to the caller, paginated. Owner/admin sees the whole organization; other members see only courses they own or whose owning team grants them learners.
Query parameters
qstring
Search by learner name/email.
statusenum
Filter by completion state.
in-progresscompleted
Responses
200List all enrollments.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/learners \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"learners": [
{
"accessEndsAt": "2026-01-31T12:00:00.000Z",
"accessStartsAt": "2026-01-31T12:00:00.000Z",
"completed": true,
"completedAt": "2026-01-31T12:00:00.000Z",
"contentId": "string",
"contentSlug": "string",
"doneSteps": 0,
"email": "learner@example.com",
"enrolledAt": "2026-01-31T12:00:00.000Z",
"id": "string",
"name": "string",
"totalSteps": 0,
"userId": "string"
}
],
"page": 0,
"pageSize": 0,
"total": 0
}
}
List a content's learners
GET/contents/{id}/learners
The content's roster with per-learner progress, enrollment status, cohort, and intake answers. Requires ownership, org owner/admin, or the learners role on the owning team.
Responses
200List a content's learners.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/contents/{id}/learners \
--header 'Authorization: Bearer cr_live_…'
{
"data": [
{
"accessEndsAt": "2026-01-31T12:00:00.000Z",
"accessStartsAt": "2026-01-31T12:00:00.000Z",
"cohortId": "string",
"completed": true,
"doneSteps": 0,
"email": "learner@example.com",
"enrolledAt": "2026-01-31T12:00:00.000Z",
"intakeAnswers": {},
"lastQuiz": {
"score": 0,
"total": 0
},
"name": "string",
"status": "active",
"totalSteps": 0,
"userId": "string"
}
]
}
Enroll a learner by email
POST/contents/{id}/learners
Admin grant — no purchase required; the seat cap still applies. The email must belong to an existing Cursare user. On a cohort-delivered offer the learner is bound to a dated cohort: pass cohortId to pick one, or omit it to follow the course's cohort policy (auto-open, or a 400 when no cohort is open).
Request body
cohortIdstring
Delivery courses only: the dated cohort (cohort) to enroll the learner into. Ignored for self-paced offers.
Responses
201Enroll a learner by email.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/contents/{id}/learners \
--request POST \
--header 'Authorization: Bearer cr_live_…' \
--header 'Content-Type: application/json' \
--data '{
"email": "learner@example.com",
"cohortId": "string"
}'
{
"data": {
"accessEndsAt": "2026-01-31T12:00:00.000Z",
"accessStartsAt": "2026-01-31T12:00:00.000Z",
"email": "learner@example.com",
"enrolledAt": "2026-01-31T12:00:00.000Z",
"userId": "string"
}
}
Revoke an enrollment
DELETE/contents/{id}/learners/{userId}
Revokes the root enrollment and its contextual curriculum access without deleting the learner's progress or history. Re-enrolling the learner restores access to the same root-scoped progress. Nested courses never receive separate enrollment rows.
Path parameters
userIdstring
The Cursare user id.
Responses
200Revoke an enrollment.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/contents/{id}/learners/{userId} \
--request DELETE \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"contentId": "string",
"unenrolled": true,
"userId": "string"
}
}
Set a learner's internal metadata
PATCH/contents/{id}/learners/{userId}
Replaces the organization's private metadata on ONE enrollment — a Stripe-style key→value bag for your own systems (CRM id, cohort, lead source, internal notes). It is REPLACE, not merge: the object you send becomes the whole bag, so send every key you want to keep (an empty object clears it). Never shown to the learner. Keys and values are strings. The response returns the resulting bag. Org-admin only (owner/admin) — metadata is not exposed on the viewer-scoped roster read.
Path parameters
userIdstring
The Cursare user id of the enrolled learner.
Responses
200Set a learner's internal metadata.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/contents/{id}/learners/{userId} \
--request PATCH \
--header 'Authorization: Bearer cr_live_…' \
--header 'Content-Type: application/json' \
--data '{
"metadata": {}
}'
{
"data": {
"contentId": "string",
"metadata": {},
"userId": "string"
}
}
Reset a learner's progress
POST/contents/{id}/learners/{userId}/reset
Restarts the course for ONE learner. DESTRUCTIVE — their progress is ERASED (every section they had completed, on the course and on its modules) and their completion is reopened; there is no undo. The enrollment SURVIVES: they keep access and their seat, and no purchase is refunded — this restarts the course, it does not remove the learner (that is unenrollLearner). Quiz history is kept, so mastery and insights survive. Drip re-anchors, because the per-section completion timestamps that pace it are wiped. A user id that isn't enrolled is a silent no-op. Confirm with a human before calling this. Use resetLearnerProgressBulk to restart several learners in one request.
Path parameters
userIdstring
The Cursare user id.
Responses
200Reset a learner's progress.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/contents/{id}/learners/{userId}/reset \
--request POST \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"contentId": "string",
"reset": true,
"userId": "string"
}
}
Reset many learners' progress
POST/contents/{id}/learners/reset
The bulk sibling of resetLearnerProgress: restarts the course for every listed learner in one shot. DESTRUCTIVE — each learner's progress is ERASED (every section they had completed, on the course and on its modules) and their completion is reopened; there is no undo. The enrollments SURVIVE: everyone keeps access and their seat, and no purchase is refunded — this restarts the course, it does not remove learners (that is unenrollLearner). Quiz history is kept, so mastery and insights survive. Drip re-anchors, because the per-section completion timestamps that pace it are wiped. Ids that aren't enrolled are silently skipped, so compare reset against the number you sent. Confirm with a human before calling this on a real roster.
Responses
200Reset many learners' progress.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/contents/{id}/learners/reset \
--request POST \
--header 'Authorization: Bearer cr_live_…' \
--header 'Content-Type: application/json' \
--data '{
"userIds": [
"string"
]
}'
{
"data": {
"contentId": "string",
"requested": 0,
"reset": 0
}
}
Get a learner's mastery signal
GET/contents/{id}/learners/{userId}/mastery
The adaptive-learning read for ONE learner on ONE course: which questions they keep missing and which modules they should revisit. Past attempts are re-graded against the CURRENT published answer key, so questions since removed simply drop out. Use it to answer 'what is this learner weak on?' and to decide what to review. Read-only, and it never exposes the answer key — you get counts per question id, not the correct options. The user must be enrolled in the content.
Path parameters
userIdstring
The Cursare user id of the enrolled learner.
Responses
200Get a learner's mastery signal.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/contents/{id}/learners/{userId}/mastery \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"contentId": "string",
"lastQuiz": {
"score": 0,
"total": 0
},
"questions": [
{
"correct": 0,
"misses": 0,
"needsWork": true,
"questionId": "string"
}
],
"reviewHints": [
{
"contentId": "string",
"routeSegment": "string",
"score": 0,
"title": "string",
"total": 0
}
],
"userId": "string"
}
}
List pending enrollment requests
GET/contents/{id}/enrollment-requests
The content's approval queue, oldest first, with each learner's intake answers. Paid requests expose their exact 30-day expiresAt; free requests return null. When refundPending is true, do not attempt a manual decision — the daily refund job owns it.
Responses
200List pending enrollment requests.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/contents/{id}/enrollment-requests \
--header 'Authorization: Bearer cr_live_…'
{
"data": [
{
"answers": {},
"email": "learner@example.com",
"expiresAt": "2026-01-31T12:00:00.000Z",
"id": "string",
"name": "string",
"refundPending": true,
"requestedAt": "2026-01-31T12:00:00.000Z",
"status": "pending",
"userId": "string"
}
]
}
Count pending enrollment requests per course
GET/enrollment-requests/summary
The approvals inbox at a glance, org-wide: how many people are waiting on staff approval, grouped by course and ordered busiest first. Cheap enough to poll — call it to say 'you have N people waiting across M courses', then drill into a specific course with listEnrollmentRequests to see who they are and approve or reject them. Owner/admin sees the whole organization; other members see only courses they own or whose owning team grants them the learners role.
Responses
200Count pending enrollment requests per course.
401Missing, invalid or revoked API key.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/enrollment-requests/summary \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"contents": [
{
"contentId": "string",
"pending": 0,
"slug": "string"
}
],
"pending": 0
}
}
Approve an enrollment request
POST/enrollment-requests/{id}/approve
Enrolls the learner (the seat cap still applies) and records the request as approved. The intake answers collected with the request travel onto the enrollment. A paid request cannot be approved at or after its 30-day expiry; it is waiting for automatic refund.
Path parameters
idstring
The enrollment request id.
Responses
200Approve an enrollment request.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/enrollment-requests/{id}/approve \
--request POST \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"approved": true
}
}
Reject an enrollment request
POST/enrollment-requests/{id}/reject
Records the request as rejected — the learner may ask again later. A paid, unrefunded purchase is refunded automatically; if that refund fails the rejection still stands, refundIssued is false, and the daily worker retries it. An optional reason is emailed to the learner best-effort. Send {} to reject without a note. An expired paid request cannot be rejected manually because its row is the durable retry queue for the daily refund.
Path parameters
idstring
The enrollment request id.
Responses
200Reject an enrollment request.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/enrollment-requests/{id}/reject \
--request POST \
--header 'Authorization: Bearer cr_live_…' \
--header 'Content-Type: application/json' \
--data '{
"reason": "string"
}'
{
"data": {
"refundIssued": true,
"rejected": true
}
}