Skip to content

LMS Integration API ​

Base URL: /api/v1/lms

Connector definitions, LMS instances and credentials, routing rules, course shells, enrollment and grade sync, drift findings, SIS CSV export, and the job and integration logs.

This replaced an earlier API

Migration 199 created the lms schema and 202 dropped the legacy records.lms_* tables. The old surface — /platforms, /platforms/{id}/urls, /platforms/{id}/campuses, /credentials/{id}, /sync-logs — no longer exists. If you are calling any of those, you are calling a subsystem that was removed.

Setup instructions live in Setting Up LMS Integration; the schema and engine internals are in LMS Integration Design.

Permission model ​

Four nodes gate this domain rather than one:

NodeCovers
ADMIN.LMS.CONNECTORSConnector types, operations, operation versions
ADMIN.LMS.INSTANCESInstances, credentials, mappings, rules, section overrides, policy
ADMIN.LMS.SECTIONSProvisioning, shells, drift findings, CSV exports
ADMIN.LMS.LOGSIntegration log, job queue, and operation preview

Two placements are deliberate:

  • Preview is gated on ADMIN.LMS.LOGS, not on the connector editor. Previewing an operation renders it against real student data.
  • Export download is gated on ADMIN.LMS.SECTIONS like the rest of that surface, even though it is a GET. The files are education records.

Connector types and operations ​

A connector type is one kind of LMS; its operations describe how to call it. Operation definitions are versioned JSONB — creating a version adds to the history rather than overwriting, so a change can be previewed before it takes effect.

http
GET /api/v1/lms/connector-types
json
{
  "results": [
    {
      "connector_type_id": 1,
      "code": "CANVAS",
      "name": "Instructure Canvas",
      "auth_strategy_code": "BEARER",
      "pagination_style": "LINK_HEADER",
      "default_rate_limit_rps": "5.00",
      "default_rate_burst": 10,
      "is_active": true
    }
  ]
}

Auth strategies: BEARER, QUERY_API_KEY, D2L_OAUTH2, OAUTH2_CLIENT_CREDENTIALS. Pagination styles: NONE, LINK_HEADER, BOOKMARK.

Seeded types are CANVAS (16 operations), MOODLE (11) and BRIGHTSPACE (10). Operation codes include verify, create_course, find_course, get_course, conclude_course, create_section, find_user, find_user_by_login, create_user, enroll_user, unenroll_user, list_enrollments, sis_import, sis_import_status, export_profile and post_grade. An operation a connector does not define is not available — Moodle has no post_grade, so a Moodle instance is offered no grade passback.

http
POST /api/v1/lms/operations/{operationID}/preview

Renders the operation against real data and returns what would be sent. Gated on ADMIN.LMS.LOGS.

Instances ​

One instance is one LMS tenant. Several may be active at once.

http
POST /api/v1/lms/instances
json
{
  "connector_type_id": 1,
  "code": "CANVAS-PROD",
  "name": "Canvas (production)",
  "base_url": "https://canvas.example.edu",
  "is_sandbox": false,
  "dry_run": true,
  "match_strategy": "SIS_ID",
  "drop_behavior": "DEACTIVATE",
  "create_missing_users": true,
  "grade_passback_enabled": false,
  "shell_name_template": "{{course.code}} - {{section.number}} ({{term.name}})",
  "rate_limit_rps": "5.00"
}
FieldValuesDefault
dry_runbool — render and log, never sendfalse
match_strategySIS_ID, EMAILSIS_ID
drop_behaviorDEACTIVATE, DELETE, CONCLUDEDEACTIVATE
create_missing_usersbool (migration 252)true
grade_passback_enabledboolfalse
is_sandbox, is_activeboolfalse / true
rate_limit_rps, rate_burstoverride the type's defaultsinherit

test-connection runs the connector's verify operation:

http
POST /api/v1/lms/instances/{instanceID}/test-connection

Credentials ​

Secrets are write-only: never returned by the API, never displayed. Listing returns metadata only — purpose, client ID, encryption key ID, activation and retirement timestamps, and rotated_from_credential_id.

http
POST /api/v1/lms/instances/{instanceID}/credentials
json
{
  "purpose": "API",
  "client_id": "…",
  "secret": "…",
  "token_url": "https://auth.example.edu/oauth/token",
  "scopes": "core:*:*",
  "activate": true
}

Activating a credential retires the incumbent in the same transaction. A credential can also be retired with no successor, which stops the integration rather than leaving a leaked token in use.

503, not 409

With no keyring configured (LMS_CRED_KEYS / LMS_CRED_ACTIVE_KEY, or the SIS_-prefixed forms on the server) this endpoint returns 503 Service Unavailable with "LMS credential encryption keys are not configured on this server".

Nothing about the request is wrong — the server has no key to seal the secret with. This was formerly reported as a 409, which read as "duplicate credential" and sent callers looking for a row that never existed. A duplicate is not reachable here: the only unique index is partial (WHERE is_active), and creation retires the incumbent in the same transaction.

Configuration endpoints keep working without a keyring; only secret-touching paths fail.

Mappings ​

Term, role and org-unit mappings translate SIS identifiers into the LMS's. All three use PUT to upsert.

http
PUT /api/v1/lms/instances/{instanceID}/term-mappings
PUT /api/v1/lms/instances/{instanceID}/role-mappings
PUT /api/v1/lms/instances/{instanceID}/org-unit-mappings

A section in a term with no term mapping produces no shell. The reason is recorded in the integration log, not returned as an error on the provisioning call.

Routing: rules, overrides, policy ​

http
GET    /api/v1/lms/rules
POST   /api/v1/lms/rules
DELETE /api/v1/lms/rules/{ruleID}
PUT    /api/v1/lms/sections/{sectionID}/override
GET    /api/v1/lms/policy
PATCH  /api/v1/lms/policy

Assignment rules route sections to an instance by org_unit_id and/or campus_id with a numeric priority (default 100, lowest wins). A section override pins one section to one instance regardless of the rules.

The policy is global, not per instance:

FieldDefault
provisioning_modeOPTIONAL
provision_trigger, provision_days_beforeMANUAL, 30
enrollment_sync_trigger, enrollment_days_beforeMANUAL, 7
conclude_policy, conclude_days_afterLEAVE_OPEN, 30
drift_missing_enrollmentFLAG
drift_extra_enrollmentFLAG
drift_missing_shellFLAG

Shells and sync ​

http
POST   /api/v1/lms/provision
GET    /api/v1/lms/dashboard
GET    /api/v1/lms/shells
GET    /api/v1/lms/shells/{shellID}/links
DELETE /api/v1/lms/shells/{shellID}
POST   /api/v1/lms/shells/{shellID}/sync-enrollments
POST   /api/v1/lms/shells/{shellID}/sync-grades
POST   /api/v1/lms/shells/{shellID}/sections
DELETE /api/v1/lms/shells/{shellID}/sections/{sectionID}

sis_course_id (migration 251) records the SIS course id actually sent, so a shell stays traceable after it is renamed in the LMS. DELETE /shells/{shellID} abandons a shell that never reached the LMS and refuses once the shell holds a course. A shell may carry several sections, which is how cross-listed sections share one LMS course.

Grade passback ​

sync-grades publishes final grades for a shell, subject to the release gate.

A grade is publishable when its latest change is Colleague-sourced or a registrar release covers it (records.grade_releases, migration 203). Two properties:

  • Fails closed — with no release gate wired, passback holds everything back.
  • No history flood — the first scan for an instance only initialises its watermark to the current maximum. Afterwards the scanner walks records.grade_change_log forward, and the release hook enqueues held grades as soon as the registrar acts.

Drift findings ​

http
GET  /api/v1/lms/drift-findings
POST /api/v1/lms/drift-findings/{findingID}/resolve

Differences between what the SIS believes and what the LMS reports: missing enrollments, extra enrollments, missing shells.

SIS CSV export ​

http
GET  /api/v1/lms/exports
POST /api/v1/lms/exports
POST /api/v1/lms/exports/preview
GET  /api/v1/lms/exports/{runID}
POST /api/v1/lms/exports/{runID}/upload
POST /api/v1/lms/exports/{runID}/cancel
GET  /api/v1/lms/exports/{runID}/files/{fileID}/download

Each connector's export_profile operation describes the CSV shape. One active run per instance is enforced by the database, so a second concurrent run is rejected rather than queued.

Jobs and logs ​

http
GET  /api/v1/lms/jobs
POST /api/v1/lms/jobs/{jobID}/retry
POST /api/v1/lms/jobs/{jobID}/cancel
GET  /api/v1/lms/integration-logs

The queue is worked with SKIP LOCKED, so workers do not contend. The dead letter is a status filter on /jobs, not a separate resource.


Complete endpoint reference ​

Every route this domain registers, with the permission node that gates it. Generated from the route table, so it is complete by construction — the sections above explain behaviour, this one guarantees nothing is missing.

Paths are relative to /api/v1/lms.

MethodEndpointPermission nodeHandler
GET/connector-typesADMIN.LMS.CONNECTORSListConnectorTypes
GET/connector-types/{typeID}/operationsADMIN.LMS.CONNECTORSListOperations
POST/connector-types/{typeID}/operationsADMIN.LMS.CONNECTORSCreateOperation
GET/operations/{operationID}/versionsADMIN.LMS.CONNECTORSListVersions
POST/operations/{operationID}/versionsADMIN.LMS.CONNECTORSCreateVersion
GET/versions/{versionID}ADMIN.LMS.CONNECTORSGetVersion
GET/instancesADMIN.LMS.INSTANCESListInstances
POST/instancesADMIN.LMS.INSTANCESCreateInstance
GET/instances/{instanceID}ADMIN.LMS.INSTANCESGetInstance
PATCH/instances/{instanceID}ADMIN.LMS.INSTANCESUpdateInstance
DELETE/instances/{instanceID}ADMIN.LMS.INSTANCESDeleteInstance
POST/instances/{instanceID}/test-connectionADMIN.LMS.INSTANCESTestConnection
GET/instances/{instanceID}/credentialsADMIN.LMS.INSTANCESListCredentials
POST/instances/{instanceID}/credentialsADMIN.LMS.INSTANCESCreateCredential
POST/instances/{instanceID}/credentials/{credentialID}/activateADMIN.LMS.INSTANCESActivateCredential
POST/instances/{instanceID}/credentials/{credentialID}/deactivateADMIN.LMS.INSTANCESDeactivateCredential
DELETE/instances/{instanceID}/credentials/{credentialID}ADMIN.LMS.INSTANCESDeleteCredential
GET/instances/{instanceID}/term-mappingsADMIN.LMS.INSTANCESListTermMappings
PUT/instances/{instanceID}/term-mappingsADMIN.LMS.INSTANCESUpsertTermMapping
DELETE/instances/{instanceID}/term-mappings/{mappingID}ADMIN.LMS.INSTANCESDeleteTermMapping
GET/instances/{instanceID}/role-mappingsADMIN.LMS.INSTANCESListRoleMappings
PUT/instances/{instanceID}/role-mappingsADMIN.LMS.INSTANCESUpsertRoleMapping
GET/instances/{instanceID}/org-unit-mappingsADMIN.LMS.INSTANCESListOrgUnitMappings
PUT/instances/{instanceID}/org-unit-mappingsADMIN.LMS.INSTANCESUpsertOrgUnitMapping
DELETE/instances/{instanceID}/org-unit-mappings/{mappingID}ADMIN.LMS.INSTANCESDeleteOrgUnitMapping
GET/rulesADMIN.LMS.INSTANCESListAssignmentRules
POST/rulesADMIN.LMS.INSTANCESCreateAssignmentRule
DELETE/rules/{ruleID}ADMIN.LMS.INSTANCESDeleteAssignmentRule
PUT/sections/{sectionID}/overrideADMIN.LMS.INSTANCESUpsertSectionOverride
GET/policyADMIN.LMS.INSTANCESGetPolicy
PATCH/policyADMIN.LMS.INSTANCESUpdatePolicy
POST/provisionADMIN.LMS.SECTIONSProvisionNow
GET/dashboardADMIN.LMS.SECTIONSDashboard
GET/shellsADMIN.LMS.SECTIONSListShells
GET/shells/{shellID}/linksADMIN.LMS.SECTIONSShellLinks
DELETE/shells/{shellID}ADMIN.LMS.SECTIONSDeleteShell
POST/shells/{shellID}/sync-enrollmentsADMIN.LMS.SECTIONSSyncEnrollmentsNow
POST/shells/{shellID}/sync-gradesADMIN.LMS.SECTIONSSyncGradesNow
POST/shells/{shellID}/sectionsADMIN.LMS.SECTIONSAddShellSection
DELETE/shells/{shellID}/sections/{sectionID}ADMIN.LMS.SECTIONSRemoveShellSection
GET/drift-findingsADMIN.LMS.SECTIONSListFindings
POST/drift-findings/{findingID}/resolveADMIN.LMS.SECTIONSResolveFinding
GET/exportsADMIN.LMS.SECTIONSListExports
POST/exportsADMIN.LMS.SECTIONSCreateExport
POST/exports/previewADMIN.LMS.SECTIONSPreviewExport
GET/exports/{runID}ADMIN.LMS.SECTIONSGetExport
POST/exports/{runID}/uploadADMIN.LMS.SECTIONSUploadExport
POST/exports/{runID}/cancelADMIN.LMS.SECTIONSCancelExport
GET/exports/{runID}/files/{fileID}/downloadADMIN.LMS.SECTIONSDownloadExportFile
POST/operations/{operationID}/previewADMIN.LMS.LOGSPreview
GET/jobsADMIN.LMS.LOGSListJobs
POST/jobs/{jobID}/retryADMIN.LMS.LOGSRetryJob
POST/jobs/{jobID}/cancelADMIN.LMS.LOGSCancelJob
GET/integration-logsADMIN.LMS.LOGSListIntegrationLog

54 routes: 6 on ADMIN.LMS.CONNECTORS, 25 on ADMIN.LMS.INSTANCES, 18 on ADMIN.LMS.SECTIONS, 5 on ADMIN.LMS.LOGS.

Student Information System Documentation