external-lms

18 endpoints.

GET/api/external-lms/servers Bearer token

List external LMS servers

Lists one organization's external LMS servers, alphabetically. Requires external-lms:read over the organization.

Parameters

NameInTypeRequiredDescription
orgquerystringyesOrganization UUID id or slug whose catalogue to list. Required.
include_archivedquerystringnoInclude archived rows. Defaults to false.

Responses

StatusDescriptionBody
200The organization's servers.ExternalLmsServerList
401The access token is missing or invalid.ErrorResponse
404No visible organization has that id or slug.ErrorResponse
500The database could not be read.ErrorResponse
POST/api/external-lms/servers Bearer token

Add 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.

Request body

application/jsonrequiredCreateExternalLmsServerRequest
FieldTypeRequiredDescription
organization_idstring (uuid)yes

Organization whose catalogue the server belongs to.

slugstringyes

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]+)*$

namestringyes

1–200 characters

descriptionstringno

Omit or send empty for no description.

at most 2000 characters

base_urlstring (uri)no

at most 2000 characters

lms_kindstringno

Free-text kind of the server, e.g. "moodle" or "scorm-cloud"; informational.

1–100 characters

Responses

StatusDescriptionBody
201The created server.ExternalLmsServer
400The request body failed validation.ErrorResponse
401The access token is missing or invalid.ErrorResponse
403The caller does not hold external-lms:write over the organization.ErrorResponse
404No visible organization has that id or slug.ErrorResponse
409The organization already has a server with that slug.ErrorResponse
500The database could not be reached, or the write failed.ErrorResponse
POST/api/external-lms/servers/{server_id}/grades/import Bearer token

Import 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

NameInTypeRequiredDescription
server_idpathstringyesUUID id, or a slug resolved within the organization given by the org query parameter.
orgquerystringnoOrganization UUID id or slug to filter by (and to resolve slug refs in).

Request body

multipart/form-datarequiredExternalGradesImportForm
FieldTypeRequiredDescription
filestring (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

StatusDescriptionBody
200One outcome per data row, in file order.IngestExternalGradesResult
400The 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
401The access token is missing or invalid.ErrorResponse
403The caller does not hold records:write over the organization.ErrorResponse
404No visible server has that id or slug.ErrorResponse
413The file is larger than 1 MB.ErrorResponse
500The database could not be reached.ErrorResponse
GET/api/external-lms/servers/{server_id}/grades Bearer token

List recently imported grades

The server's most recently imported grades, newest first. Requires external-lms:read over the server's organization.

Parameters

NameInTypeRequiredDescription
server_idpathstringyesUUID id, or a slug resolved within the organization given by the org query parameter.
orgquerystringnoOrganization UUID id or slug to filter by (and to resolve slug refs in).
limitqueryintegernoPage size, 1-200. Defaults to 50.

Responses

StatusDescriptionBody
200The grades, newest first.ExternalCourseGradeList
401The access token is missing or invalid.ErrorResponse
404No visible server has that id or slug.ErrorResponse
500The database could not be read.ErrorResponse
POST/api/external-lms/servers/{server_id}/grades Bearer token

Import 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

NameInTypeRequiredDescription
server_idpathstringyesUUID id, or a slug resolved within the organization given by the org query parameter.
orgquerystringnoOrganization UUID id or slug to filter by (and to resolve slug refs in).

Request body

application/jsonrequiredIngestExternalGradesRequest
FieldTypeRequiredDescription
gradesarray of ExternalGradeRowyes

1–1000 items

Responses

StatusDescriptionBody
200One outcome per submitted row, in row order.IngestExternalGradesResult
400The request body failed validation, or the server is archived.ErrorResponse
401The access token is missing or invalid.ErrorResponse
403The caller does not hold records:write over the organization.ErrorResponse
404No visible server has that id or slug.ErrorResponse
500The database could not be reached.ErrorResponse
GET/api/external-lms/servers/{server_id}/user-mappings Bearer token

List 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

NameInTypeRequiredDescription
server_idpathstringyesUUID id, or a slug resolved within the organization given by the org query parameter.
orgquerystringnoOrganization UUID id or slug to filter by (and to resolve slug refs in).

Responses

StatusDescriptionBody
200The server's mappings, by external user id.ExternalLmsUserMappingList
401The access token is missing or invalid.ErrorResponse
404No visible server has that id or slug.ErrorResponse
500The database could not be read.ErrorResponse
PUT/api/external-lms/servers/{server_id}/user-mappings Bearer token

Create 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

NameInTypeRequiredDescription
server_idpathstringyesUUID id, or a slug resolved within the organization given by the org query parameter.
orgquerystringnoOrganization UUID id or slug to filter by (and to resolve slug refs in).

Request body

application/jsonrequiredUpsertExternalLmsUserMappingRequest
FieldTypeRequiredDescription
external_user_idstringyes

The LMS's own identity of the learner. Writing an existing one repoints it.

1–200 characters

user_idstring (uuid)yes

users.id of the directory member the identity resolves to.

Responses

StatusDescriptionBody
200The server's mappings after the write.ExternalLmsUserMappingList
400The request body failed validation.ErrorResponse
401The access token is missing or invalid.ErrorResponse
403The caller does not hold external-lms:write over the organization.ErrorResponse
404No visible server has that id or slug, or the user is not in the organization's directory.ErrorResponse
500The database could not be reached, or the write failed.ErrorResponse
DELETE/api/external-lms/servers/{server_id}/user-mappings/{external_user_id} Bearer token

Delete 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

NameInTypeRequiredDescription
server_idpathstringyesUUID id, or a slug resolved within the organization given by the org query parameter.
external_user_idpathstringyesThe LMS's own identity of the learner, URL-encoded.
orgquerystringnoOrganization UUID id or slug to filter by (and to resolve slug refs in).

Responses

StatusDescriptionBody
200The server's mappings after the delete.ExternalLmsUserMappingList
401The access token is missing or invalid.ErrorResponse
403The caller does not hold external-lms:write over the organization.ErrorResponse
404No visible server has that id or slug, or no mapping has that identity.ErrorResponse
500The database could not be reached, or the write failed.ErrorResponse
GET/api/external-lms/servers/{server_id}/courses Bearer token

List a server's courses

Lists one server's catalogue courses, alphabetically. Requires external-lms:read over the server's organization.

Parameters

NameInTypeRequiredDescription
server_idpathstringyesUUID id, or a slug resolved within the organization given by the org query parameter.
orgquerystringnoOrganization UUID id or slug to filter by (and to resolve slug refs in).
include_archivedquerystringnoInclude archived rows. Defaults to false.

Responses

StatusDescriptionBody
200The server's courses.ExternalLmsCourseList
401The access token is missing or invalid.ErrorResponse
404No visible server has that id or slug.ErrorResponse
500The database could not be read.ErrorResponse
POST/api/external-lms/servers/{server_id}/courses Bearer token

Add 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

NameInTypeRequiredDescription
server_idpathstringyesUUID id, or a slug resolved within the organization given by the org query parameter.
orgquerystringnoOrganization UUID id or slug to filter by (and to resolve slug refs in).

Request body

application/jsonrequiredCreateExternalLmsCourseRequest
FieldTypeRequiredDescription
external_course_idstringyes

The LMS's own identifier of the course. Unique within the server; immutable.

1–200 characters

namestringyes

1–200 characters

descriptionstringno

at most 2000 characters

course_urlstring (uri)no

at most 2000 characters

passing_scorenumberno

Score required to pass, 0-100. Omit to let the imported passed flag decide.

minimum 0 · maximum 100

subject_matter_idsarray 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

StatusDescriptionBody
201The created course.ExternalLmsCourse
400The request body failed validation.ErrorResponse
401The access token is missing or invalid.ErrorResponse
403The caller does not hold external-lms:write over the organization.ErrorResponse
404No visible server has that id or slug, or a subject matter is not the organization's own.ErrorResponse
409The server already catalogues a course with that external id.ErrorResponse
500The database could not be reached, or the write failed.ErrorResponse
GET/api/external-lms/courses/{course_id}/permanent-deletion Bearer token

Preview 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

NameInTypeRequiredDescription
course_idpathstring (uuid)yesUUID id of the catalogue course.

Responses

StatusDescriptionBody
200What the deletion would take with it.DeletionImpact
401The access token is missing or invalid.ErrorResponse
403The caller can read this course but does not hold external-lms:delete.ErrorResponse
404No visible course has that id.ErrorResponse
500The database could not be read.ErrorResponse
DELETE/api/external-lms/courses/{course_id}/permanent-deletion Bearer token

Permanently 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

NameInTypeRequiredDescription
course_idpathstring (uuid)yesUUID id of the catalogue course.

Responses

StatusDescriptionBody
204The course, its grades, their records and its rules are gone.—
401The access token is missing or invalid.ErrorResponse
403The caller can read this course but does not hold external-lms:delete.ErrorResponse
404No visible course has that id.ErrorResponse
500The database could not be reached, or the write failed.ErrorResponse
GET/api/external-lms/servers/{server_id} Bearer token

Read one external LMS server

Requires external-lms:read over the server's organization.

Parameters

NameInTypeRequiredDescription
server_idpathstringyesUUID id, or a slug resolved within the organization given by the org query parameter.
orgquerystringnoOrganization UUID id or slug to filter by (and to resolve slug refs in).

Responses

StatusDescriptionBody
200The server.ExternalLmsServer
401The access token is missing or invalid.ErrorResponse
404No visible server has that id or slug.ErrorResponse
500The database could not be read.ErrorResponse
PATCH/api/external-lms/servers/{server_id} Bearer token

Update 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

NameInTypeRequiredDescription
server_idpathstringyesUUID id, or a slug resolved within the organization given by the org query parameter.
orgquerystringnoOrganization UUID id or slug to filter by (and to resolve slug refs in).

Request body

application/jsonrequiredUpdateExternalLmsServerRequest
FieldTypeRequiredDescription
namestringno

1–200 characters

descriptionstring | nullno

Send null to clear the description.

at most 2000 characters

base_urlstring (uri) | nullno

Send null to clear the URL.

at most 2000 characters

lms_kindstring | nullno

Send null to clear the kind.

1–100 characters

Responses

StatusDescriptionBody
200The updated server.ExternalLmsServer
400The request body failed validation.ErrorResponse
401The access token is missing or invalid.ErrorResponse
403The caller can read this server but does not hold external-lms:write.ErrorResponse
404No visible server has that id or slug.ErrorResponse
500The database could not be reached, or the write failed.ErrorResponse
DELETE/api/external-lms/servers/{server_id} Bearer token

Archive 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

NameInTypeRequiredDescription
server_idpathstringyesUUID id, or a slug resolved within the organization given by the org query parameter.
orgquerystringnoOrganization UUID id or slug to filter by (and to resolve slug refs in).

Responses

StatusDescriptionBody
200The archived server.ExternalLmsServer
401The access token is missing or invalid.ErrorResponse
403The caller can read this server but does not hold external-lms:write.ErrorResponse
404No visible server has that id or slug.ErrorResponse
500The database could not be reached, or the write failed.ErrorResponse
GET/api/external-lms/courses/{course_id} Bearer token

Read one catalogue course

Requires external-lms:read over the course's organization.

Parameters

NameInTypeRequiredDescription
course_idpathstring (uuid)yesUUID id of the catalogue course.

Responses

StatusDescriptionBody
200The course.ExternalLmsCourse
401The access token is missing or invalid.ErrorResponse
404No visible course has that id.ErrorResponse
500The database could not be read.ErrorResponse
PATCH/api/external-lms/courses/{course_id} Bearer token

Update 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

NameInTypeRequiredDescription
course_idpathstring (uuid)yesUUID id of the catalogue course.

Request body

application/jsonrequiredUpdateExternalLmsCourseRequest
FieldTypeRequiredDescription
namestringno

1–200 characters

descriptionstring | nullno

at most 2000 characters

course_urlstring (uri) | nullno

Send null to clear the URL.

at most 2000 characters

passing_scorenumber | nullno

Send null to clear the pass mark (the imported flag then decides).

minimum 0 · maximum 100

subject_matter_idsarray 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

StatusDescriptionBody
200The updated course.ExternalLmsCourse
400The request body failed validation.ErrorResponse
401The access token is missing or invalid.ErrorResponse
403The caller can read this course but does not hold external-lms:write.ErrorResponse
404No visible course has that id, or a subject matter is not the organization's own.ErrorResponse
500The database could not be reached, or the write failed.ErrorResponse
DELETE/api/external-lms/courses/{course_id} Bearer token

Archive 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

NameInTypeRequiredDescription
course_idpathstring (uuid)yesUUID id of the catalogue course.

Responses

StatusDescriptionBody
200The archived course.ExternalLmsCourse
401The access token is missing or invalid.ErrorResponse
403The caller can read this course but does not hold external-lms:write.ErrorResponse
404No visible course has that id.ErrorResponse
500The database could not be reached, or the write failed.ErrorResponse