Overview 

Restful API v1.5 is a significant upgrade from v1, which is being sunset at the end of 2022. While new clients can onboard directly to the new version, v1 clients must upgrade to take advantage of new features and capabilities.

For more information on new v1.5 features, see Appendix A.
For more details on why the upgrade and sunset are necessary, see Appendix B.

To make upgrading easier, we have:

  1. Incorporated a backward compatibility setting which minimizes client code changes
  2. Created this comprehensive guide to take you through the entire upgrade process
  3. Trained and equipped your dedicated Client Success Manager, Account Manager and Absorb  Support team so they are ready to answer your questions and provide support

Upgrade Steps

Here are the steps we suggest Absorb LMS Admins take to initiate and complete the upgrade process.

Step One: Plan Out the Changes

Gather the technical team and explain the need and timing of the upgrade. To evaluate the effort required for your organization, first identify how the API is used in your system:

  1. Find every place in your code that calls the API

    Note: At minimum the path will have to change to access the new Service Gateway

  2. Identify where paginated endpoints are called

These endpoints will require special treatment. Instead of a single GET, multiple calls will be required to access all items. Also consider if the use of filtering and/or sorting will reduce the need to GET all resources. When used correctly, these optional parameters can result in a more targeted data set, returned in optimal order for your operation.

List of paginated endpoints:

/coupons

/courses

/courses/GUID/enrollments

/courses/GUID/lessons/GUID/enrollments

/departments

/groups

/instructorLedCourses

/onlineCourses

/roles/sessions

/users

/users/GUID/enrollments

/users/GUID/certificates  

For an example of pagination loop code written in JavaScript, see Appendix C.

Note: even though highly filtered GET can result in a single record being returned

  1. Identify any additional code change requirements

In most cases, path and pagination are the limit of code changes required. To confirm this is the case, or identify additional code change requirements, we recommend the technical team review the full list of endpoint differences found in Appendix D.

Step Two: Implement the Changes

  1. Setup your development environment to deploy code changes to your sandbox environment, where experimentation and testing can take place safely.
  2. Search and replace API call path from “api/Rest/v1/” to “myabsorb.com/
  3. For the paginated endpoints:
  4. Set up a loop to keep making GET calls until no records returned (null returned)
  5. For each call supply an “offset” parameter. This is the current page, starting with 0.
  6. For each call supply the same “limit” parameter. This is the maximum items to be returned per page.
  7. Optionally add sorting by specifying which fields to sort return results by, and by which order
  8. Optionally add filtering by supplying the filter query string adhering to OData syntax.
  9. Apply any additional changes identified in Step 3 of the Plan Out the Changes

Step Three: Verify the Changes

  1. First run all your calls in v1 and v1.5, and comparing the results. Any differences found during regression testing should be expected and desired.
  2. Layer on calls to leverage the new v1.5 functionality, such as accessing new endpoints. Ensure the new endpoints deliver the desired results and ensure no regressions are introduced to existing functionality.
  3. Ideally compare results produced in the Sandbox environment with those on production. For example: if your primary API use case is daily reporting, run your Sandbox changes against a copy of production data and compare the end of day reports.

Step Four: Deploy to Production

  1. Before you deploy:
    • Notify your dedicated Client Success Manager or Account Manager of your deployment timeline, so any support requests can be flagged and expedited
    • Ensure you have rollback strategy in place, including undoing any data changes, just in case the deployment either cannot be completed or produces unexpected results
  2. Deploy to production and start benefitting from the upgraded API
  3. Let us know how things are going so we can celebrate your success

Appendix A: What is new in Restful API v1.5?

For existing API clients upgrading to v1.5, these new features are available:

  • All API requests will go through a Service Gateway, which was introduced to provide increased security and reliability
  • Support for pagination, sorting and filtering on selected GET endpoints
  • User email address no longer required when updating user attributes
  • The user resource has new endpoints, allowing you to:
    • Update, sort and filter by custom fields 
    • Access last login 
    • Access earned competencies
  • Optionally, clients can utilize Oath2 for security

For new API clients onboarding directly to v1.5, and upgrading clients that decide not to utilize the backward compatibility setting, all the above features are available plus:

  • Swagger generated and maintained API documentation
  • Requests and responses in camel case
  • Where pagination is used, the response includes a wrapper with the number of elements in the collection

Pagination

Supply both ‘offset [numeric value]’ & ‘limit [numeric value]’ parameters on specific GET requests to paginate returned results.
Offset is the current page, with 0 being the first. Increment this value on successive calls until no records are returned.
If no pagination parameters supplied, default pagination settings are “offset=0&limit=1000”.

Note: to access records beyond the first page, the endpoint must be called again with an offset value one increment higher than the last call, until no results are returned.

For example:
https://[path]/[endpoint]?offset=0&limit=100

See Appendix D for sample pagination looping code written in JavaScript

Sorting

Sorting is a completely optional feature available on endpoints where pagination is supported.
Combined with Filtering, Sorting extends API user abilities to find information you need in the format that you need it.

Use ‘_sort’ query string parameter on specific GET requests.
Supply a comma-separated list of sort keys. The default sort order is ascending.
Prefixing '-' before the sort key will return results in descending order.

For example:
https:// https://[path]/[endpoint]?_sort=-relevance&term=abc

Filtering

Filtering is a completely optional feature available on endpoints where pagination is supported.
One of the most useful features that will allow more precise queries is Filtering.
The primary use case will be to find the relevant records based on search of a particular category.
Filtering is available on most parameters, but not all. Further details are available in the attached technical document.  

Use ‘_filter’ query string parameter on specific GET requests. Supply a comma-separated list of filter operations to be performed. Supported operations (see Appendix B) include:

  • Operators: eq, ne, gt, ge, it, le, and, or, not, ()
  • Functions: substringof, endswith, startswith, tolower, toupper

For example:
https://[path]/[endpoint]?_filter=startsWith(lastname, ‘leb’) or dataAdded ge datetime‘1999-0306T20:38:07Z’

Appendix B: Why is this upgrade necessary?

Restful API v1 has been around since the beginning of Absorb LMS. Since that time, a few things have happened:

  1. The User Experience, modules, services and underlying data models have changed in response to new clients and new ways of learning
  2. Technologies and industry standards have changed, in response to new threats and opportunities
  3. Absorb has seen a huge upsurge in LMS usage, placing an ever-increasing burden on existing infrastructure servicing API requests
  4. In the past year increased monitoring and API clients have reported processing delays, timeouts and service interruptions

After a thorough review, we created a new Restful API version with:

  1. A service gateway now manages and satisfies all API requests
  2. New architecture to handle immediate and planned changes
  3. API versioning, so the use of future changes will be optional
  4. Added non-optional pagination support to high use “open-ended” GET endpoints

Lastly it was necessary to move all API clients over to v1.5 and sunset v1 because all API clients share the same request servicing infrastructure. The only way to remove all security threats, service interruptions and processing bottlenecks is to upgrade all to v1.5.

This upgrade unfortunately represents a "breaking change" (because it "breaks the API contract" and requires client code changes). While this was absolutely necessary to deliver the required architectural, technological and functional upgrades required to achieve the responsiveness and reliability our clients demand, we understand this is an exceptional, one-time event. By making major changes now, and by introducing API versioning, Absorb aims to avoid mandatory upgrades from happening in the future.

Appendix C: Example Pagination Loop Written in JavaScript

<html>

<body>

<script>

const _url = 'http://localhost:5001/api/rest/v1_5/users';

// main function to get a list of users.

// Note, it is not possible to actually execute this within a webpage outside of Absorb due to CORS rescritions.

async function GetUsersAsync(){

    // create a list to accumulate users over multiple calls to the endpoint.

    var totalUsers = [];

    var done = false;

    var pageOffsetIndex = 0;

    var recordsPerPage = Number(document.querySelector('input').value);

    var myLabel = document.getElementById('myLabel');

    // do the loop now until the endpoint returns an empty list.

    while (!done){

        // call the endpoint here.

        var users = await GetUsersAsyncDownload(pageOffsetIndex, recordsPerPage);

        // increase the page index so we can specify the next chunk of data to download.

        pageOffsetIndex++;

        // were any records returned? If not then end.

        if (users == null || users.length == 0){

            done = true;

        }

        else{

            // this is just for this sample webpage. Displays the results to the screen as we download.

            myLabel.innerHTML = `Downloading ${users.length}...`;

            DisplayUsers(users);// display the partial list received.

            totalUsers = totalUsers.concat(users); // add to the list of users.

        }

    }

    // display the final total list.

    myLabel.innerHTML = `Full List Complete: ${totalUsers.length} users`;

    DisplayUsers(totalUsers);

}

// call the endpoint to retrieve one page of data.

async function GetUsersAsyncDownload(pageOffset, pageSize){

    var options = GetHeaders();

    var result = await fetch(_url + "?" + new URLSearchParams({limit: pageSize, offset: pageOffset}), options);

    return result.json();

}

// prepare the http headers for the call.

function GetHeaders(){

    return {

        'method': 'GET',

        'headers': {

            credentials: 'include',

            'Content-Type': 'application/json',

            'Authorization': '<your-authorization-token-goes-here>'

        }

    }

}

// display list of users to the html page.

function DisplayUsers(users){

    var index = 1;

    var table = document.getElementById('myTable');

    var html = '<tr><td></td><td>First Name</td><td>Last Name</td></tr>';

    for (var user of users){

        html += `<tr><td>${index}</td><td>${user.FirstName}</td><td>${user.LastName}</td></tr>`;

        index++;

    }

    table.innerHTML = html;

}

</script>

<style>

    table{

        margin-top: 10px;

    }

    table td{

        width: 150px;

    }

    table >  tbody > tr:first-child > td{

        font-weight: 700;

        text-decoration: underline;

    }

</style>

Records Per Page:</br>

<input type="Number" value="5"/></br>

<button onclick="GetUsersAsync();">Get List of Users (with async/wait)</button>

</br></br></br></br>

<div id="myLabel"></div>

<table id="myTable">

</table>

</body>

</html>

Appendix D: Full List of v1 vs v1.5 Endpoint Differences

Controller 

Endpoint

Differences

usersController 

POST api/Rest/v1/users/upload?key={key} 

"EmailAddress" is not a mandatory field in v1.5 

POST api/Rest/v1/users?key={key} 

"EmailAddress" is not a mandatory field in v1.5 

POST api/Rest/v1_5/createabsorbaccount?key={key} 

"EmailAddress" is not a mandatory field in v1.5 

GET api/Rest/v1/mydetails 

Added new field "DateEdited" and "LastLoginDate" in v1.5 

PUT api/Rest/v1/users/{userId}/user-management-settings 

Identical 

GET api/Rest/v1/Users/{id} 

Added new field "DateEdited" and "LastLoginDate" in v1.5 

PUT api/Rest/v1/Users/{id} 

"EmailAddress" is not a mandatory field in v1.5   Added new field "DateEdited" and "LastLoginDate" in v1.5 

EnrollmentsController 

GET api/Rest/v1/courses/{courseId}/sessions/{sessionId}/enrollments?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/users/{userId}/enrollments?status={status}&modifiedSince={modifiedSince}&offset={offset}&limit={limit} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/users/{userId}/enrollments/{courseId}?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/users/{userId}/grades/{courseId}?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/users/{userId}/enrollments/{courseId}/chapters/{chapterId}/lessons/{lessonId}?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/users/{userId}/enrollments/{courseId}/chapters?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/users/{userId}/enrollments/{courseId}/chapters/{chapterId}?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/users/{userId}/enrollments/{courseId}/sessions?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/users/{userId}/enrollments/{courseId}/sessions/{sessionId}?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/users/{userId}/enrollments/{courseId}/lessons?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/users/{userId}/enrollments/{courseId}/lessons/{lessonId}?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/users/{userId}/enrollments/{courseId}/chapters/{chapterId}/lessons?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/bulk/studentcourses?userIds[0]={userIds[0]}&userIds[1]={userIds[1]}&courseIds[0]={courseIds[0]}&courseIds[1]={courseIds[1]} 

Added new field "DateEdited" in v1.5 

POST api/Rest/v1/users/{userId}/enrollments/{courseId}?reEnroll={reEnroll} 

Added new field "DateEdited" in v1.5 

POST api/Rest/v1/users/{userId}/enrollments/{courseId}/session/{sessionId}?reEnroll={reEnroll}&cancelSession={cancelSession} 

Identical 

POST api/Rest/v1/users/{userId}/enrollments?reEnroll={reEnroll} 

Identical 

GET api/Rest/v1/users/{userId}/enrollments/{courseId}/lessons/{lessonId}/attempts 

Added new field "DateEdited" in v1.5 

POST api/Rest/v1/users/{userId}/enrollments/{courseId}/lessons/{lessonId}/startattempt 

Identical 

POST api/Rest/v1/attempts/{attemptId}/finish 

Identical 

POST api/Rest/v1/users/{userId}/enrollments/{courseId}/lessons/{lessonId}/attempts/{attemptId}/finish 

Identical 

POST api/Rest/v1/enrollments/unenroll 

Identical 

GET api/Rest/v1/myenrollments?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/myenrollments/{courseId}?status={status}&modifiedSince={modifiedSince} 

Added new field "DateEdited" in v1.5 

POST api/Rest/v1/enroll/{courseId} 

Identical 

GET api/Rest/v1/Enrollments/{id} 

Added new field "DateEdited" in v1.5 

DepartmentsController 

GET api/Rest/v1/departments/{id} 

Identical 

POST api/Rest/v1/departments 

Identical 

POST api/Rest/v1/createdepartment 

Identical 

AttendanceController 

GET api/rest/v1/instructor-led-course-sessions/{sessionId}/session-schedules/{sessionScheduleId}/attendance 

Identical 

PUT api/rest/v1/instructor-led-course-sessions/{sessionId}/session-schedules/{sessionScheduleId}/attendance/{id} 

Identical 

POST api/rest/v1/instructor-led-course-sessions/{sessionId}/session-schedules/{sessionScheduleId}/attendance 

Identical 

CourseBundlesController 

GET api/Rest/v1/CourseBundles/ForSale?id={id}&catId={catId}&from={from}&to={to} 

Added new field "DateEdited" and "CourseType" in v1.5 

GET api/Rest/v1/CourseBundles 

Added new field "DateEdited" and "CourseType" in v1.5 

GET api/Rest/v1/CourseBundles/{id} 

Added new field "DateEdited" and "CourseType" in v1.5 

CurriculumsController 

GET api/Rest/v1/Curriculums 

Added new field "DateEdited" and "CourseType" in v1.5 

GET api/Rest/v1/Curriculums?id={id} 

Added new field "DateEdited" and "CourseType" in v1.5 

GET api/Rest/v1/Curriculums/ForSale?id={id}&catId={catId}&from={from}&to={to} 

Added new field "DateEdited" and "CourseType" in v1.5 

GET api/Rest/v1/Curriculums/CurriculumGroup?id={id} 

Added new field "DateEdited" and "CourseType" in v1.5 

POST api/Rest/v1/Curriculums/MultipleCurriculumGroups 

Identical 

CustomFieldDefinitions
Controller 

GET api/Rest/v1/CustomFieldDefinitions 

Identical 

GET api/Rest/v1/CustomFieldDefinitions/{id} 

Identical 

EnrollmentKeyController 

GET api/Rest/v1/enrollmentkeys/{enrollmentKeyId} 

Identical 

GET api/Rest/v1/enrollmentkeys?externalId={externalId} 

Identical 

POST api/Rest/v1/enrollmentkeys 

Identical 

POST api/Rest/v1/enrollmentkeys/{enrollmentKeyId} 

Identical 

LessonsController 

GET api/Rest/v1/courses/{courseId}/lessons 

Identical 

GET api/Rest/v1/courses/{courseId}/lessons/{id} 

Identical 

GET api/Rest/v1/courses/{courseId}/chapters/{chapterId}/lessons/{id} 

Identical 

GET api/Rest/v1/Lessons 

Identical 

GET api/Rest/v1/Lessons/{id} 

Identical 

PrerequisitesController 

GET api/Rest/v1/course/{courseId}/prerequisites 

Identical 

GET api/Rest/v1/Prerequisites 

Identical 

GET api/Rest/v1/Prerequisites/{id} 

Identical 

ResourcesController 

GET api/Rest/v1/courses/{courseId}/resources 

Remvoed field "ModuleId" in V1 

 

GET api/Rest/v1/courses/{courseId}/resources/{id} 

Remvoed field "ModuleId" in V1 

 

GET api/Rest/v1/users/{userId}/resources 

Remvoed field "ModuleId" in V1 

 

GET api/Rest/v1/users/{userId}/enrollments/{courseId}/resources 

Remvoed field "ModuleId" in V1 

 

GET api/Rest/v1/enrollments/{courseId}/myresources 

Remvoed field "ModuleId" in V1 

 

GET api/Rest/v1/enrollments/myresources 

Remvoed field "ModuleId" in V1 

 

GET api/Rest/v1/Resources 

Remvoed field "ModuleId" in V1 

 

GET api/Rest/v1/Resources/{id} 

Remvoed field "ModuleId" in V1 

TagsController 

GET api/Rest/v1/Tags 

Identical 

GET api/Rest/v1/Tags/{id} 

Identical 

POST api/Rest/v1/Tags 

Identical 

PUT api/Rest/v1/Tags/{id} 

Identical 

VenuesController 

GET api/Rest/v1/Venues 

Identical 

GET api/Rest/v1/Venues/{id} 

Identical 

POST api/Rest/v1/Venues 

Identical 

PUT api/Rest/v1/Venues/{id} 

Identical 

instructorLedCourse
Controller 

GET api/Rest/v1/InstructorLedCourses/{id} 

Added new field "DateEdited" and "CourseType" in v1.5 

groupsController 

GET api/Rest/v1/Groups/{id} 

Identical 

CoursesController 

GET api/Rest/v1/users/{userId}/courses 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/users/{userId}/catalog 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/courses/forsale?id={id}&categoryId={categoryId}&from={from}&to={to} 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/mycourses 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/mycatalog 

Added new field "DateEdited" in v1.5 

GET api/Rest/v1/Courses/{id} 

Added new field "DateEdited" in v1.5 

onlineCoursesController 

GET api/Rest/v1/OnlineCourses/{id} 

Added new field "DateEdited" and "CourseType" in v1.5 

certificatesController 

GET api/Rest/v1/users/{userId}/certificates/{id} 

Identical 

 

GET api/Rest/v1/courses/{courseId}/certificates?includeExpired={includeExpired}&acquiredDate={acquiredDate}&expiryDate={expiryDate} 

Identical 

 

GET api/Rest/v1/courses/{courseId}/certificates/{id} 

Identical 

 

GET api/Rest/v1/Certificates/{id} 

Identical 

sessionsController  

POST api/Rest/v1/courses/sessiondetails 

Identical 

GET api/Rest/v1/users/{userId}/sessiondetails 

Identical 

GET api/Rest/v1/courses/{courseId}/sessions 

Identical 

GET api/Rest/v1/courses/{courseId}/sessions/{id} 

Identical 

GET api/Rest/v1/Sessions/{id} 

Identical 

SessionSchedules
Controller 

GET api/Rest/v1/SessionSchedules 

Identical 

GET api/Rest/v1/SessionSchedules/{id} 

Identical 

ChaptersController 

GET api/Rest/v1/courses/{courseId}/chapters 

Identical 

GET api/Rest/v1/courses/{courseId}/chapters/{id} 

Identical 

GET api/Rest/v1/Chapters 

Identical 

GET api/Rest/v1/Chapters/{id} 

Identical 

CourseVersionController 

GET api/Rest/v1/course/{courseId}/versions 

Identical 

GET api/Rest/v1/CourseVersions/{id} 

Identical 

EcommerceTransactionsController 

GET api/Rest/v1/ecommerce/transactions/{transactionId} 

Identical 

GET api/Rest/v1/ecommerce/transactions?UserId={UserId}&CourseId={CourseId}&TransactionDate={TransactionDate}&TransactionCompletedDate={TransactionCompletedDate}&PaymentGatewayTransactionId={PaymentGatewayTransactionId} 

Identical 

POST api/Rest/v1/ecommerce/transactions/{transactionId}/status 

Identical 

LanguagesController 

GET api/Rest/v1/Languages 

Identical 

GET api/Rest/v1/Languages/{id} 

Identical 

MessagesController 

GET api/Rest/v1/users/{userId}/messages 

Identical 

GET api/Rest/v1/users/{userId}/messages/sent 

Identical 

GET api/Rest/v1/users/{userId}/messages/received 

Identical 

GET api/Rest/v1/Messages/{id} 

Identical 

ProvincesController 

GET api/Rest/v1/Provinces?countryId={countryId} 

Identical 

GET api/Rest/v1/Provinces/{id} 

Identical 

ResourceCategories
Controller  

GET api/Rest/v1/ResourceCategories 

Identical 

GET api/Rest/v1/ResourceCategories/{id} 

Identical 

POST api/Rest/v1/ResourceCategories 

Identical 

PUT api/Rest/v1/ResourceCategories/{id} 

Identical 

CategoriesController 

GET api/Rest/v1/Categories 

Identical 

GET api/Rest/v1/Categories/{id} 

Identical 

CountriesController 

GET api/Rest/v1/Countries 

Identical 

GET api/Rest/v1/Countries/{id} 

Identical 

 

 

 

 

Was this article helpful?
0 out of 0 found this helpful