external-lms
18 endpoints.
/api/external-lms/servers Bearer tokenList external LMS servers
Lists one organization's external LMS servers, alphabetically. Requires external-lms:read over the organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| org | query | string | yes | Organization UUID id or slug whose catalogue to list. Required. |
| include_archived | query | string | no | Include archived rows. Defaults to false. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The organization's servers. | ExternalLmsServerList |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 404 | No visible organization has that id or slug. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/external-lms/servers Bearer tokenAdd an external LMS server
Adds a server to an organization's catalogue. Descriptive metadata only — the LRS never connects to it. Requires external-lms:write over the organization.
| Field | Type | Required | Description |
|---|---|---|---|
| organization_id | string (uuid) | yes | Organization whose catalogue the server belongs to. |
| slug | string | yes | Unique within the organization. Slugs are lowercase letters and digits in words separated by single hyphens, like moodle-eu. 1–100 characters · matches ^[a-z0-9]+(-[a-z0-9]+)*$ |
| name | string | yes | 1–200 characters |
| description | string | no | Omit or send empty for no description. at most 2000 characters |
| base_url | string (uri) | no | at most 2000 characters |
| lms_kind | string | no | Free-text kind of the server, e.g. "moodle" or "scorm-cloud"; informational. 1–100 characters |
Responses
| Status | Description | Body |
|---|---|---|
| 201 | The created server. | ExternalLmsServer |
| 400 | The request body failed validation. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller does not hold external-lms:write over the organization. | ErrorResponse |
| 404 | No visible organization has that id or slug. | ErrorResponse |
| 409 | The organization already has a server with that slug. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |
/api/external-lms/servers/{server_id}/grades/import Bearer tokenImport course grades from a CSV file
Bulk variant of the grades import: one uploaded gradebook CSV instead of a JSON batch, with the same one-outcome-per-row semantics — malformed cells fail their row, not the file, so a fixed export can simply be uploaded again (already-imported rows come back as duplicate). Requires records:write over the organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| server_id | path | string | yes | UUID id, or a slug resolved within the organization given by the org query parameter. |
| org | query | string | no | Organization UUID id or slug to filter by (and to resolve slug refs in). |
| Field | Type | Required | Description |
|---|---|---|---|
| file | string (binary) | yes | CSV file, at most 1 MB: a header row naming an `external_course_id`, `score` (0-100) and `completed_at` (ISO date) column, plus an `external_user_id` and/or `learner_user_id` column, in any order; `passed` and `external_attempt_id` columns are optional. Header matching is case-insensitive and extra columns are ignored. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | One outcome per data row, in file order. | IngestExternalGradesResult |
| 400 | The file is not parseable CSV, a required column is missing from the header, the file has no (or too many) data rows, or the server is archived. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller does not hold records:write over the organization. | ErrorResponse |
| 404 | No visible server has that id or slug. | ErrorResponse |
| 413 | The file is larger than 1 MB. | ErrorResponse |
| 500 | The database could not be reached. | ErrorResponse |
/api/external-lms/servers/{server_id}/grades Bearer tokenList recently imported grades
The server's most recently imported grades, newest first. Requires external-lms:read over the server's organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| server_id | path | string | yes | UUID id, or a slug resolved within the organization given by the org query parameter. |
| org | query | string | no | Organization UUID id or slug to filter by (and to resolve slug refs in). |
| limit | query | integer | no | Page size, 1-200. Defaults to 50. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The grades, newest first. | ExternalCourseGradeList |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 404 | No visible server has that id or slug. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/external-lms/servers/{server_id}/grades Bearer tokenImport course grades
Imports a batch of grades from this server. Each created grade becomes a completed learning record (owned by the organization) carrying the score as evidence, plus a queryable grade row. Rows succeed or fail one by one — re-submitting a batch after fixing a mapping is the normal workflow; already-imported rows come back as duplicate. Learners are identified by external_user_id through the server's user mappings, or directly by user_id. Requires records:write over the organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| server_id | path | string | yes | UUID id, or a slug resolved within the organization given by the org query parameter. |
| org | query | string | no | Organization UUID id or slug to filter by (and to resolve slug refs in). |
| Field | Type | Required | Description |
|---|---|---|---|
| grades | array of ExternalGradeRow | yes | 1–1000 items |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | One outcome per submitted row, in row order. | IngestExternalGradesResult |
| 400 | The request body failed validation, or the server is archived. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller does not hold records:write over the organization. | ErrorResponse |
| 404 | No visible server has that id or slug. | ErrorResponse |
| 500 | The database could not be reached. | ErrorResponse |
/api/external-lms/servers/{server_id}/user-mappings Bearer tokenList a server's user mappings
Lists how the server's external user identities resolve to directory members. Requires external-lms:read over the server's organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| server_id | path | string | yes | UUID id, or a slug resolved within the organization given by the org query parameter. |
| org | query | string | no | Organization UUID id or slug to filter by (and to resolve slug refs in). |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The server's mappings, by external user id. | ExternalLmsUserMappingList |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 404 | No visible server has that id or slug. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/external-lms/servers/{server_id}/user-mappings Bearer tokenCreate or repoint a user mapping
Maps one external user identity to a directory member; writing an existing identity repoints it. Grades already imported keep the member they resolved to at import time. Requires external-lms:write over the server's organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| server_id | path | string | yes | UUID id, or a slug resolved within the organization given by the org query parameter. |
| org | query | string | no | Organization UUID id or slug to filter by (and to resolve slug refs in). |
| Field | Type | Required | Description |
|---|---|---|---|
| external_user_id | string | yes | The LMS's own identity of the learner. Writing an existing one repoints it. 1–200 characters |
| user_id | string (uuid) | yes | users.id of the directory member the identity resolves to. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The server's mappings after the write. | ExternalLmsUserMappingList |
| 400 | The request body failed validation. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller does not hold external-lms:write over the organization. | ErrorResponse |
| 404 | No visible server has that id or slug, or the user is not in the organization's directory. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |
/api/external-lms/servers/{server_id}/user-mappings/{external_user_id} Bearer tokenDelete a user mapping
Unmaps one external user identity; grades already imported through it are untouched. Requires external-lms:write over the server's organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| server_id | path | string | yes | UUID id, or a slug resolved within the organization given by the org query parameter. |
| external_user_id | path | string | yes | The LMS's own identity of the learner, URL-encoded. |
| org | query | string | no | Organization UUID id or slug to filter by (and to resolve slug refs in). |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The server's mappings after the delete. | ExternalLmsUserMappingList |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller does not hold external-lms:write over the organization. | ErrorResponse |
| 404 | No visible server has that id or slug, or no mapping has that identity. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |
/api/external-lms/servers/{server_id}/courses Bearer tokenList a server's courses
Lists one server's catalogue courses, alphabetically. Requires external-lms:read over the server's organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| server_id | path | string | yes | UUID id, or a slug resolved within the organization given by the org query parameter. |
| org | query | string | no | Organization UUID id or slug to filter by (and to resolve slug refs in). |
| include_archived | query | string | no | Include archived rows. Defaults to false. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The server's courses. | ExternalLmsCourseList |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 404 | No visible server has that id or slug. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/external-lms/servers/{server_id}/courses Bearer tokenAdd a course to a server's catalogue
Adds one course, keyed by the LMS's own course identifier — the id grades arrive under. Requires external-lms:write over the server's organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| server_id | path | string | yes | UUID id, or a slug resolved within the organization given by the org query parameter. |
| org | query | string | no | Organization UUID id or slug to filter by (and to resolve slug refs in). |
| Field | Type | Required | Description |
|---|---|---|---|
| external_course_id | string | yes | The LMS's own identifier of the course. Unique within the server; immutable. 1–200 characters |
| name | string | yes | 1–200 characters |
| description | string | no | at most 2000 characters |
| course_url | string (uri) | no | at most 2000 characters |
| passing_score | number | no | Score required to pass, 0-100. Omit to let the imported passed flag decide. minimum 0 · maximum 100 |
| subject_matter_ids | array of string (uuid) | no | Subject matters of the server's own organization to file imported grade records under; all of them. Omit for an untagged course. at most 50 items |
Responses
| Status | Description | Body |
|---|---|---|
| 201 | The created course. | ExternalLmsCourse |
| 400 | The request body failed validation. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller does not hold external-lms:write over the organization. | ErrorResponse |
| 404 | No visible server has that id or slug, or a subject matter is not the organization's own. | ErrorResponse |
| 409 | The server already catalogues a course with that external id. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |
/api/external-lms/courses/{course_id}/permanent-deletion Bearer tokenPreview permanently deleting a catalogue course
Counts everything DELETE on this path would remove: the imported grades (with the learning records the import created) and the proof rules requiring a pass of the course. Nothing is changed. Requires external-lms:delete over the organization — a grant separate from external-lms:write, which only archives.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| course_id | path | string (uuid) | yes | UUID id of the catalogue course. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | What the deletion would take with it. | DeletionImpact |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller can read this course but does not hold external-lms:delete. | ErrorResponse |
| 404 | No visible course has that id. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/external-lms/courses/{course_id}/permanent-deletion Bearer tokenPermanently delete a catalogue course
Hard-deletes the course — unlike DELETE /external-lms/courses/{course_id}, which only archives. Every imported grade is deleted together with the learning record the import created for it, so passes that satisfied certification rules no longer count; the external_course_pass rules pointing at the course are deleted too. Irreversible. Preview the cost with GET first. Requires external-lms:delete over the organization — a grant separate from external-lms:write, which only archives.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| course_id | path | string (uuid) | yes | UUID id of the catalogue course. |
Responses
| Status | Description | Body |
|---|---|---|
| 204 | The course, its grades, their records and its rules are gone. | — |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller can read this course but does not hold external-lms:delete. | ErrorResponse |
| 404 | No visible course has that id. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |
/api/external-lms/servers/{server_id} Bearer tokenRead one external LMS server
Requires external-lms:read over the server's organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| server_id | path | string | yes | UUID id, or a slug resolved within the organization given by the org query parameter. |
| org | query | string | no | Organization UUID id or slug to filter by (and to resolve slug refs in). |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The server. | ExternalLmsServer |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 404 | No visible server has that id or slug. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/external-lms/servers/{server_id} Bearer tokenUpdate an external LMS server
Changes a server's name, description, URL or kind. The slug is immutable. Requires external-lms:write over the organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| server_id | path | string | yes | UUID id, or a slug resolved within the organization given by the org query parameter. |
| org | query | string | no | Organization UUID id or slug to filter by (and to resolve slug refs in). |
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | no | 1–200 characters |
| description | string | null | no | Send null to clear the description. at most 2000 characters |
| base_url | string (uri) | null | no | Send null to clear the URL. at most 2000 characters |
| lms_kind | string | null | no | Send null to clear the kind. 1–100 characters |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The updated server. | ExternalLmsServer |
| 400 | The request body failed validation. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller can read this server but does not hold external-lms:write. | ErrorResponse |
| 404 | No visible server has that id or slug. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |
/api/external-lms/servers/{server_id} Bearer tokenArchive an external LMS server
Soft-deletes a server by setting archived_at. Its courses, mappings and imported grades stay readable, but the server stops taking grades. Requires external-lms:write over the organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| server_id | path | string | yes | UUID id, or a slug resolved within the organization given by the org query parameter. |
| org | query | string | no | Organization UUID id or slug to filter by (and to resolve slug refs in). |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The archived server. | ExternalLmsServer |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller can read this server but does not hold external-lms:write. | ErrorResponse |
| 404 | No visible server has that id or slug. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |
/api/external-lms/courses/{course_id} Bearer tokenRead one catalogue course
Requires external-lms:read over the course's organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| course_id | path | string (uuid) | yes | UUID id of the catalogue course. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The course. | ExternalLmsCourse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 404 | No visible course has that id. | ErrorResponse |
| 500 | The database could not be read. | ErrorResponse |
/api/external-lms/courses/{course_id} Bearer tokenUpdate a catalogue course
Changes a course's name, description, URL, pass mark or subject tags (replaced as a set). The external course id is immutable — grades key on it. Requires external-lms:write over the organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| course_id | path | string (uuid) | yes | UUID id of the catalogue course. |
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | no | 1–200 characters |
| description | string | null | no | at most 2000 characters |
| course_url | string (uri) | null | no | Send null to clear the URL. at most 2000 characters |
| passing_score | number | null | no | Send null to clear the pass mark (the imported flag then decides). minimum 0 · maximum 100 |
| subject_matter_ids | array of string (uuid) | no | Replaces the course's subject-matter tags; send an empty array to stop tagging imported records. Records already imported keep their tags. at most 50 items |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The updated course. | ExternalLmsCourse |
| 400 | The request body failed validation. | ErrorResponse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller can read this course but does not hold external-lms:write. | ErrorResponse |
| 404 | No visible course has that id, or a subject matter is not the organization's own. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |
/api/external-lms/courses/{course_id} Bearer tokenArchive a catalogue course
Soft-deletes a course by setting archived_at. Imported grades stay, but the course stops taking new ones. Requires external-lms:write over the organization.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| course_id | path | string (uuid) | yes | UUID id of the catalogue course. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The archived course. | ExternalLmsCourse |
| 401 | The access token is missing or invalid. | ErrorResponse |
| 403 | The caller can read this course but does not hold external-lms:write. | ErrorResponse |
| 404 | No visible course has that id. | ErrorResponse |
| 500 | The database could not be reached, or the write failed. | ErrorResponse |