Sync / roster
Bidirectional roster and grade synchronization between Ellucian Colleague Student Information System and Canvas LMS using LTI Advantage NRPS/RPPI and REST APIs.
Ellucian Colleague stores student, enrollment, and academic term data in its proprietary structure (STU, REG, CRSE, SECT, TRM tables), while Canvas LMS organizes information around courses, sections, and users with its own identifiers and conventions.
The integration layer—typically implemented as middleware using MuleSoft, Dell Boomi, or a custom service—transforms Colleague's STU, REG, and SECT records into Canvas /courses, /sections, and /enrollments API calls, with LTI Advantage Names and Role Provisioning Services (NRPS) handling real-time roster updates.
This is difficult because Colleague's academic hierarchy (DEPT → CRSE → SECT) doesn't map 1:1 to Canvas's course/section model, term codes differ between systems, and institutions often run Colleague on legacy infrastructure that requires careful API gateway design to avoid overwhelming the source system.
A working piece from this integration — no sign-up. The full build handles the edge cases, safeguards, and cutover.
/* Ellucian Colleague → Canvas LMS enrollment mapping */ Ellucian Colleague DB → Canvas API payload ───────────────────────────────────────────────────────────── SEC.SECONDARY_SECONDARY →section_idSEC.COURSE →course_idSTU.ID →user_idSTVPN.ROLE →enrollment_type/* 'StudentEnrollment' | 'TeacherEnrollment' */ STU.EMAIL →user[login]oruser[email]STU.FIRST_NAME + STU.LAST_NAME →user[name]/* Canvas REST API — batch enrollment POST */ POST https://institution.instructure.com/api/v1/sections/:section_id/enrollments /* Payload */ [ { "user_id": "S00123456", "enrollment_type": "StudentEnrollment", "notify": false, "course_section_id": "SEC-2024-FALL-BIOL101-001" }, { "user_id": "faculty1", "enrollment_type": "TeacherEnrollment", "notify": true, "course_section_id": "SEC-2024-FALL-BIOL101-001" } ] /* Canvas LTI Advantage — NRPS membership pull (from Colleague tool) */ GET /api/lti/courses/{course_id}/names_and_roles?rlid={resource_link_id} /* Response: list of members with status, role, name per LTI 1.3 NRPS spec */
How we'd take this from discovery to a production-safe cutover — the phases, the canonical mapping, and the edge cases that bite.
This integration provisions course rosters via LTI Advantage Names and Role Provisioning Service (NRPS) and synchronizes grades back to Ellucian Colleague via REST APIs. The sync runs on a configurable schedule (recommended: every 15 minutes for rosters, daily batch for final grades) and maintains idempotency using Canvas id fields as canonical keys.
STVTERM) and map to Canvas term identifiers. Request LTI Developer Keys from Canvas (Client ID and Deployment ID). Register Ellucian as an LTI 1.3 Tool Provider in the Canvas Admin settings under Settings → Apps → LTI Advantage. Confirm that Ellucian Colleague Web API authentication (typically SOAP or REST with token exchange) is enabled and retrieve a service account with read access to STU_CRED, STU, CRSE_TERM_INFO, and SECTIONS.https://imsglobal.org/lti/nrps/scope/contextmembership.readonly scope. Note the Platform OIDC Initiation URL, Auth Token Endpoint, and the registered JWKS URI that Ellucian will use to validate the platform's public key. Configure the Roster Provisioning setting to "Full Portal" to receive all enrolled users regardless of course section visibility.SECTIONS.CRSE_TERM_INFO_ID combined with STVTERM.TERM_CODE forms the Canvas course_id via a prefix convention (e.g., CRSE-{CRSE_ID}-{TERM}). Store this mapping in a staging table INT_CANVAS_COURSE_MAP to support lookups during sync runs.STU_CRED for active enrollments where CRNC_STATUS = 'A' and ENRL_STATUS = 'E'. For each enrollment, retrieve the student's STU.STU_ID, STU.EMAIL_ADDRESS, and role (INstructor vs student). POST membership records to Canvas using the NRPS endpoint /api/lti/courses/{course_id}/names_and_roles. Use the status field to reflect enrollment changes: Active, Deleted, or Inactive. Log all sourced IDs returned by Canvas in INT_CANVAS_SOURCED_IDS for downstream grade correlation.GET /api/v1/courses/{course_id}/students/submissions. Match each submission to a Colleague STU_CRED record using the Canvas user_id → STU.STU_ID lookup from the sourced ID table. Map Canvas letter grades to Colleague grade codes using STVGRDE. For midterm and final grade posting, write to STU_CRED.GRDE_CODE and set STU_CRED.GRDE_DATE. Wrap all grade writes in a transaction and set a sync flag GRADE_SYNC_FLAG = 'Y' to prevent duplicate submissions.STU_CRED screen. Compare row counts between STU_CRED filtered by term and Canvas Enrollments API output to verify completeness.INT_SYNC_CTRL.SYNC_MODE = 'PROD' to redirect to live endpoints. Retain the previous manual enrollment process as a fallback for 72 hours. Schedule a reconciliation report that outputs mismatches: students in Colleague but not Canvas, and vice versa. If data corruption occurs, set INT_SYNC_CTRL.SYNC_MODE = 'ROLLBACK' to halt automated writes and re-enable manual grade entry.| Ellucian Colleague Field | Canvas Identifier / Endpoint | Notes |
|---|---|---|
STU.STU_ID | user.id (from NRPS) and user.sis_user_id | Primary student identifier; maps to Canvas SIS ID field |
STU.EMAIL_ADDRESS | user.login_id | Used for identity resolution during LTI launch |
SECTIONS.CRSE_TERM_INFO_ID + STVTERM.TERM_CODE | course.id | Construct course_id as CTI-{CRSE_TERM_INFO_ID}-{TERM}; stored in INT_CANVAS_COURSE_MAP |
STU_CRED.ROLE values: IN, TA | membership.role | Map IN → TeacherEnrollment; TA → TaEnrollment; all others → StudentEnrollment |
STU_CRED.ENRL_STATUS = 'E' | membership.status = Active | Only active Colleague enrollments sync; withdrawn status maps to Deleted |
STU_CRED.GRDE_CODE | submission.score and submission.grade | Letter grade written to Canvas assignment; reverse-mapped via STVGRDE |
CRSE_TERM_INFO.SECTION_NUMBER | section.id | Canvas sections correspond to Colleague course sections; used for section-level grade posting |
STU_CRED.CRED_HRS | course.credit_hours (custom attribute) | Optional; push credit hours to Canvas as a custom course setting for advising views |
STU_CRED records for the same course (enrolled + waitlisted). The sync must process only records where ENRL_STATUS = 'E' and ignore 'W'. Failing to filter produces duplicate Canvas enrollments and breaks grade posting.STU_CRED.ROLE flips from one IN to another, Canvas NRPS sends a status: Inactive for the departing instructor. The pipeline must handle this gracefully rather than treating it as a deletion and orphaning existing grades.CRSE_TERM_INFO_ID (cross-listed sections), the sync writes to both and double-posts grades to Colleague. Detect cross-listings by querying SECTIONS.CROSS_LIST_ID before syncing and deduplicate on CRSE_TERM_INFO_ID.assignment.name) + course context, not by cached assignment.id. Stale cached IDs cause grade writes to 404.GRDE_DATE in server local time; Canvas submissions use UTC. A grade posted at 11:45 PM local may appear as the next day in Canvas. Normalize all timestamps to UTC before comparison in reconciliation reports.course.grading_standard_enabled flag and the assignment posted attribute before attempting grade submission.INT_SYNC_CTRL with values MAINTENANCE, PARALLEL, PROD, and ROLLBACK. In PARALLEL mode, the sync runs but writes to a shadow table (STU_CRED_SHADOW) rather than STU_CRED, allowing a side-by-side comparison for 5 business days. On promotion to PROD, enable live writes and disable manual grade entry in Colleague for synced sections. Retain the ability to revert to ROLLBACK within 72 hours by restoring STU_CRED from the pre-sync snapshot and re-enabling manual entry. Schedule an automated reconciliation job (run STU_CRED row count vs Canvas enrollments count by TERM_CODE) to run daily for 30 days post-cutover and alert on any drift exceeding 0.1% of enrollment volume. Reading the reference is free. Delivering it under liability — with the safeguards that keep production running through the cutover — is what we do.