Purple LMS API Documentation

Machine-readable OpenAPI 3.0 specification: https://marketplace.moveworks.com/api/purple-suite/lms/openapi.json

The consolidated all-apps document at https://marketplace.moveworks.com/api/purple-suite/openapi.json can be narrowed to this app with ?apps=lms, or combined with other apps comma-separated (e.g. ?apps=lms,itsm).

Overview

Learning Management System (LMS) API — manage a course catalog and learner enrollments. Resembles tools like Cornerstone, Docebo, and SAP SuccessFactors Learning. Pairs with Purple HRS skills/goals for skill-gap-driven learning. --- **Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.

Endpoints

GET /api/purple-suite/lms/courses

List courses in the Purple Learn catalog

Browse the learning catalog of this instance (Cornerstone/Docebo-style): each Course has a title, level, format (video/article/path), duration and skills_taught. This is the catalog of available training, NOT anyone's progress — for who is taking what, use lms__list__enrollments. Supports server-side OData querying — $filter, $search, $orderby, $select, $top, $skip, $count (MCP clients see these as _filter, _search, _orderby, _select, _top, _skip, _count because $ is not a legal tool-input key). Example: $filter=level eq 'beginner' and $search=leadership.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Course

    • id string
    • title string required
    • description string
    • skills_taught arraySkills this course develops (matches Purple HRS employee skills).
      Skills this course develops (matches Purple HRS employee skills).

      array of:

      string

    • level string
    • duration_minutes integer
    • format string
    • provider string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

POST /api/purple-suite/lms/courses

Add a course to the Purple Learn catalog

Creates a new catalog entry (title required; optionally level, format, provider, duration_minutes and the skills_taught it develops). Creating a course does not assign it to anyone — use lms__enroll__courses to put a learner on it.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Course

  • id string
  • title string required
  • description string
  • skills_taught arraySkills this course develops (matches Purple HRS employee skills).
    Skills this course develops (matches Purple HRS employee skills).

    array of:

    string

  • level string
  • duration_minutes integer
  • format string
  • provider string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Course

  • id string
  • title string required
  • description string
  • skills_taught arraySkills this course develops (matches Purple HRS employee skills).
    Skills this course develops (matches Purple HRS employee skills).

    array of:

    string

  • level string
  • duration_minutes integer
  • format string
  • provider string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/lms/courses/{id}

Get one Purple Learn course

Fetches a single catalog course by id (e.g. CRS-0001), including its skills_taught, level, format and duration. Use it to confirm a course exists or check what skills it covers before enrolling someone.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Course

  • id string
  • title string required
  • description string
  • skills_taught arraySkills this course develops (matches Purple HRS employee skills).
    Skills this course develops (matches Purple HRS employee skills).

    array of:

    string

  • level string
  • duration_minutes integer
  • format string
  • provider string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/lms/courses/{id}

Delete a Purple Learn course

DESTRUCTIVE and irreversible: removes the course from the catalog. Enrollment records that point at this course_id are not cleaned up and will reference a missing course, so prefer retiring a course via lms__update__courses unless it was created in error.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/lms/courses/{id}

Update a Purple Learn course

Partially updates a catalog course (title, description, level, format, provider, duration_minutes, skills_taught). Affects the catalog entry only — existing learner enrollments and their progress are untouched.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Course

  • id string
  • title string required
  • description string
  • skills_taught arraySkills this course develops (matches Purple HRS employee skills).
    Skills this course develops (matches Purple HRS employee skills).

    array of:

    string

  • level string
  • duration_minutes integer
  • format string
  • provider string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Course

  • id string
  • title string required
  • description string
  • skills_taught arraySkills this course develops (matches Purple HRS employee skills).
    Skills this course develops (matches Purple HRS employee skills).

    array of:

    string

  • level string
  • duration_minutes integer
  • format string
  • provider string
  • createdAt string
  • updatedAt string
404
Not found

POST /api/purple-suite/lms/courses/{id}/enroll

Enroll a learner in a course

The normal way to put someone on training: creates a not_started Enrollment for the given learner (identify them by employee_id or user_email) against the course in the path. Fails with 422 if that learner already has an active (not_started/in_progress) enrollment for the course, and 404 if the course does not exist. Prefer this over lms__create__enrollments, which skips those checks.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json — required

  • employee_id stringLearner's HRS employee ID. Provide this or user_email.
  • user_email stringLearner's email. Provide this or employee_id.

Responses

201
Enrollment created

application/json

Enrollment

  • id string
  • course_id string
  • learner stringResolved learner identifier (employee_id or user_email). Set automatically on enroll.
  • employee_id string
  • user_email string
  • status string
  • progress_pct integer
  • score number
  • enrolled_at string
  • completed_at string
  • createdAt string
  • updatedAt string
400
Missing learner (employee_id or user_email)
404
Course not found
422
Learner already has an active enrollment for this course

GET /api/purple-suite/lms/enrollments

List learner enrollments (training progress)

An Enrollment is one learner's run through one course — course_id, learner (employee_id or user_email), status (not_started | in_progress | completed | dropped), progress_pct, score and completion date. Use this for 'who has completed X', overdue training or compliance reporting; use lms__list__courses for the catalog itself. Supports server-side OData querying — $filter, $search, $orderby, $select, $top, $skip, $count (MCP clients see these as _filter, _search, _orderby, _select, _top, _skip, _count because $ is not a legal tool-input key). Example: $filter=status eq 'completed' and course_id eq 'CRS-0001'.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Enrollment

    • id string
    • course_id string
    • learner stringResolved learner identifier (employee_id or user_email). Set automatically on enroll.
    • employee_id string
    • user_email string
    • status string
    • progress_pct integer
    • score number
    • enrolled_at string
    • completed_at string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

POST /api/purple-suite/lms/enrollments

Create an enrollment record directly

Low-level create that writes an Enrollment exactly as given — useful for backfilling historical or already-completed training. To simply put a learner on a course, prefer lms__enroll__courses, which resolves the learner, defaults the status and blocks duplicate active enrollments; this operation performs none of those checks.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Enrollment

  • id string
  • course_id string
  • learner stringResolved learner identifier (employee_id or user_email). Set automatically on enroll.
  • employee_id string
  • user_email string
  • status string
  • progress_pct integer
  • score number
  • enrolled_at string
  • completed_at string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Enrollment

  • id string
  • course_id string
  • learner stringResolved learner identifier (employee_id or user_email). Set automatically on enroll.
  • employee_id string
  • user_email string
  • status string
  • progress_pct integer
  • score number
  • enrolled_at string
  • completed_at string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/lms/enrollments/{id}

Get one learner enrollment

Fetches a single Enrollment by id (e.g. ENR-0001) with its current status, progress_pct, score and timestamps. Use it to check one learner's progress on one course after finding the id via lms__list__enrollments.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Enrollment

  • id string
  • course_id string
  • learner stringResolved learner identifier (employee_id or user_email). Set automatically on enroll.
  • employee_id string
  • user_email string
  • status string
  • progress_pct integer
  • score number
  • enrolled_at string
  • completed_at string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/lms/enrollments/{id}

Delete a learner enrollment

DESTRUCTIVE and irreversible: erases the enrollment and with it the learner's recorded progress, score and completion history for that course. To take someone off a course while keeping the audit trail, set status to 'dropped' via lms__update__enrollments instead.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/lms/enrollments/{id}

Update enrollment progress or status

Partially updates an Enrollment — typically progress_pct, score, or status among not_started | in_progress | completed | dropped (use this to drop or resume someone). A completed enrollment cannot be reopened. To finish a course properly, prefer lms__complete__enrollments, which also sets progress to 100 and stamps completed_at.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Enrollment

  • id string
  • course_id string
  • learner stringResolved learner identifier (employee_id or user_email). Set automatically on enroll.
  • employee_id string
  • user_email string
  • status string
  • progress_pct integer
  • score number
  • enrolled_at string
  • completed_at string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Enrollment

  • id string
  • course_id string
  • learner stringResolved learner identifier (employee_id or user_email). Set automatically on enroll.
  • employee_id string
  • user_email string
  • status string
  • progress_pct integer
  • score number
  • enrolled_at string
  • completed_at string
  • createdAt string
  • updatedAt string
404
Not found

POST /api/purple-suite/lms/enrollments/{id}/complete

Mark an enrollment complete

The correct way to finish training: transitions the enrollment to completed, forces progress_pct to 100, stamps completed_at and stores an optional final score. This is a one-way transition — a completed enrollment can never be reopened — and it fails with 422 if the enrollment is already completed or was dropped. Use lms__update__enrollments for partial progress instead.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json

  • score numberFinal assessment score, if any.

Responses

200
Enrollment completed

application/json

Enrollment

  • id string
  • course_id string
  • learner stringResolved learner identifier (employee_id or user_email). Set automatically on enroll.
  • employee_id string
  • user_email string
  • status string
  • progress_pct integer
  • score number
  • enrolled_at string
  • completed_at string
  • createdAt string
  • updatedAt string
404
Enrollment not found
422
Enrollment already completed or dropped