Introduction
Restful API v1.5 is a significant upgrade from v1. 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:
- Incorporated a backward compatibility setting that minimizes client code changes.
- Created this comprehensive guide to take you through the entire upgrade process.
- 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:
-
Find every place in your code that calls the API.
- Every API call will require changes to the service path and https header.
- See Step Two for more details on the required changes.
- Every API call will require changes to the service path and https header.
-
Identify where paginated endpoints are called.
- In addition to the changes outlined in the previous step, paginated endpoints will require special treatment. Instead of a single GET, multiple calls will be required to access all items.
- Also, consider if 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.
- In addition to the changes outlined in the previous step, paginated endpoints will require special treatment. Instead of a single GET, multiple calls will be required to access all items.
See Step Two for more details on using filtering and sorting.
List of paginated endpoints:
• /coupons • /courses • /courses/GUID/enrollments • /courses/GUID/lessons/GUID/enrollments • /departments • /groups • /instructorLedCourses |
• /onlineCourses |
List of pagination endpoints when called with the 1.5.1 version parameter:
- /api/rest/v1_5/lessons
- /api/rest/v1_5/courses/[GUID]/certificates
- /api/rest/v1_5/tags
- /api/rest/v1_5/instructor-led-course-sessions/[GUID]/session-schedules/[GUID]/attendance
- /api/rest/v1_5/session-schedules
- /api/rest/v1_5/my-courses, /api/rest/v1_5/my-catalog,
- /api/rest/v1_5/curriculums,
- /api/rest/v1_5/course-bundles
Even though a highly filtered GET can result in a single record being returned, it is best practice always to specify pagination parameters.
-
Identify any additional code change requirements.
- We recommend the technical team review the complete list of endpoint differences found in Appendix C.
Step Two: Implement the Changes
-
Due to updated processes, the Production and Sandbox keys must be different for the Restful API v1.5 to function.
Notify your dedicated Client Success Manager or Account Manager of your plans to use Restful API v1.5 in your Sandbox. They will arrange for your Sandbox API key to be regenerated. To avoid any disruption of existing Sandbox development and testing using Restful API v1, please ensure that all in-process testing that depends on the current key is completed.
This process will not change your Production API key. It's important to note that the Restful v1.5 key is the same as your Restful API v1 key. If you continue to use v1 as you transition to v1.5, please use the same API key for both.
- Setup your development environment to deploy code changes to your sandbox environment, where experimentation and testing can take place safely.
- For all API calls:
- Change the service path, depending on the environment you are using, to "rest.myabsorb.com" for production or "rest.sandbox.myabsorb.com" for the sandbox
- Add “x-api-key: [your private API key]” to the request header
-
Example: “Post https://rest.myabsorb.com/authenticate” (assuming the request header contains the line “x-api-key: [your private API key]”
- Change the service path, depending on the environment you are using, to "rest.myabsorb.com" for production or "rest.sandbox.myabsorb.com" for the sandbox
-
For calls to paginated endpoints:
It is critical to update paginated endpoints in your code. These endpoints, which previously returned full data sets, will now return a default of 20 records.
This means that if no updates are made, the data returned from the API will not always be the full data available to you. Your systems which rely on the full data, may be impacted.
• Set up a loop to keep making GET calls until no records are returned (null returned)
• For each call, supply an “_offset” parameter. This is the current page, starting with 0.
• For each call, supply the same “_limit” parameter. This maximum number of items to be returned per page can be set as high as 1000.
• Optionally add sorting by specifying which fields to sort return results by and by which order.
• Optionally add filtering by supplying the filter query string adhering to OData syntax. - For calls to versioned endpoints:
When developing your code, for certain endpoints the option exists to use the published (or base) version of the endpoint, or a newer version of that endpoint.
The intention is that for clients with an integration in place, revisions to the API which otherwise would be considered breaking will not impact their current integrations. Clients wishing to benefit from the additional functionality offered by the revision to the endpoint can specify when calling that they'd like the new version.
Versioned endpoints will be highlighted with an additional header parameter available on the documentation. First time REST API V1.5 users are encouraged to use the newest version of the endpoints to receive the latest features.
Versioned endpoints will accept a new version header x-api-version. This header is not required, but is recommended for consumers that may not always have the resources to adopt the latest changes to the API.
In addition, each response from the API will include a version header to help indicate which version you are currently receiving for the given endpoint from the API.
Note that Absorb may, at its discretion, update the base version of the endpoint. If this occurs, communication will precede the change with enough notice for clients to make changes before their integrations are impacted.
Also note that while backwards compatibility is enabled, you will be restricted to version 1.5.0.
- Apply any additional changes identified in Step 3 of the Plan Out the Changes.
Step Three: Verify the Changes
- First, run all your calls in v1 and v1.5, comparing the results. Any differences found during regression testing should be expected and desired.
- 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.
- 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
- 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 a rollback strategy in place, including undoing any data changes, just in case the deployment cannot be completed or produces unexpected results.
- Deploy to production and start benefitting from the upgraded API.
- 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 introduced to provide increased security and reliability.
- Support for pagination, sorting and filtering on selected GET endpoints.
- User email address is no longer required when updating user attributes.
- New endpoints allow you to update custom fields and access earned competencies.
- On the user endpoints, you can now:
- Sort and filter by custom fields
- Access last login
- Optionally, clients can utilize OAuth 2.0 for additional 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 (these documents are always available but only accurately reflect API usage when backward compatibility is not in use)
- Requests and responses in camel case
- Where pagination is used, the response includes a wrapper with the following additional information
- totalItems - the total items returned
- limit - how many items returned on each page
- offset - current offset
- returnedItems - how many items were returned on the current page
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=20”.
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
Versioned APIs
The API supports multiple versions concurrently to allow consumers to update their integrations at their own pace. Each request will accept a new version header x-api-version. This header is not required but is recommended for consumers that may not always have the resources to adopt the latest changes to the API.
In addition, each response from the API will include a version header to help indicate which version you are currently receiving for the given endpoint from the API.
Versioned endpoints will be noted with an additional header parameter per endpoint basis within the API documentation. While backward compatibility is enabled, you will be restricted to version 1.5.0.
Sorting
Sorting is a completely optional feature available on endpoints where pagination is supported.
Combined with Filtering, Sorting extends API user abilities to find the information you need in the format you need.
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://[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 documentation.
Use ‘_filter’ query string parameter on specific GET requests. Supply a comma-separated list of filter operations to be performed. Supported operations 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:
- The User Experience, modules, services, and underlying data models have changed in response to new clients and new ways of learning.
- Technologies and industry standards have changed in response to new threats and opportunities.
- Absorb has seen a huge upsurge in LMS usage, placing an ever-increasing burden on existing infrastructure servicing API requests.
- 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:
- A service gateway now manages and satisfies all API requests
- New architecture to handle immediate and planned changes
- API versioning so the use of future changes will be optional.
- 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 duplicate request servicing infrastructure. The only way to improve security and remove service interruptions and processing bottlenecks is to upgrade all users to Version 1.5.
This upgrade, unfortunately, represents a "breaking change" (because it "breaks the API contract" and requires client code changes). While this was 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 prevent mandatory upgrades from happening in the future.
Appendix C
List of v1 vs. v1.5 Endpoint Differences
v1 Endpoint |
v1.5 Differences |
---|---|
GET /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 |
GET /categories |
Identical |
GET /categories/{id} |
Identical |
GET /certificates/{id} |
Identical |
GET /chapters |
Identical |
GET /chapters/{id} |
Identical |
GET /countries |
Identical |
GET /countries/{id} |
Identical |
GET /coupons/{CouponId} |
Identical |
GET /coupons?offset={offset}&limit={limit} |
Identical |
GET /course/{courseid}/prerequisites |
Identical |
GET /course/{courseid}/versions |
Identical |
GET /CourseBundles |
Added new field "DateEdited" and "CourseType" in v1.5 |
GET /CourseBundles/{id} |
Added new field "DateEdited" and "CourseType" in v1.5 |
GET /CourseBundles/forsale?id={id}&catid={catid}&from={from}&to={to} |
Added new field "DateEdited" and "CourseType" in v1.5 |
GET /courses/{courseid}/certificates/{id} |
Identical |
GET /courses/{CourseId}/certificates?IncludeExpired={IncludeExpired}&AcquiredDate={AcquiredDate}&ExpiryDate={ExpiryDate} |
Identical |
GET /courses/{courseid}/certificates?includeexpired={includeexpired}&acquireddate={acquireddate}&expirydate={expirydate} |
Identical |
GET /courses/{courseid}/chapters |
Identical |
GET /courses/{courseid}/chapters/{chapterid}/lessons/{id} |
Identical |
GET /courses/{courseid}/chapters/{id} |
Identical |
GET /courses/{CourseId}/enrollments?Status={Status}&ModifiedSince={ModifiedSince}&Offset={Offset}&Limit={Limit} |
Added new field "DateEdited" in v1.5 |
GET /courses/{courseid}/lessons |
Identical |
GET /courses/{courseid}/lessons/{id} |
Identical |
GET /courses/{CourseId}/lessons/{LessonId}/enrollments?Status={Status}&ModifiedSince={ModifiedSince}&Offset={Offset}&Limit={Limit} |
Added new field "DateEdited" in v1.5 |
GET /courses/{courseid}/resources |
Removed field "ModuleId" in V1 |
GET /courses/{courseid}/resources/{id} |
Removed field "ModuleId" in V1 |
GET /courses/{courseid}/sessions |
Identical |
GET /courses/{courseid}/sessions/{id} |
Identical |
GET /courses/{courseid}/sessions/{sessionid}/enrollments?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /courses/{id} |
Added new field "DateEdited" in v1.5 |
GET /courses/forsale?id={id}&categoryid={categoryid}&from={from}&to={to} |
Added new field "DateEdited" in v1.5 |
GET /courses?ModifiedSince={ModifiedSince}&ExternalId={ExternalId}&Offset={Offset}&Limit={Limit} |
Added new field "DateEdited" in v1.5 |
GET /CourseVersions/{id} |
Identical |
GET /credits/credit-types |
Identical |
GET /credits/users/{UserId}/enrollments/{EnrollmentId} |
Identical |
GET /Curriculums |
Added new field "DateEdited" and "CourseType" in v1.5 |
GET /Curriculums/CurriculumGroup?id={id} |
Added new field "DateEdited" and "CourseType" in v1.5 |
GET /Curriculums/ForSale?id={id}&catid={catid}&from={from}&to={to} |
Added new field "DateEdited" and "CourseType" in v1.5 |
GET /Curriculums?id={id} |
Added new field "DateEdited" and "CourseType" in v1.5 |
GET /CustomFieldDefinitions |
Identical |
GET /CustomFieldDefinitions/{id} |
Identical |
GET /departments/{id} |
Identical |
GET /departments?DepartmentName={DepartmentName}&ExternalId={ExternalId}&Offset={Offset}&Limit={Limit} |
Identical |
GET /ecommerce/transactions/{transactionid} |
Identical |
GET /ecommerce/transactions?userid={userid}&courseid={courseid}&transactiondate={transactiondate}&transactioncompleteddate={transactioncompleteddate}&paymentgatewaytransactionid={paymentgatewaytransactionid} |
Identical |
GET /enrollmentkeys/{enrollmentkeyid} |
Identical |
GET /enrollmentkeys?externalid={externalid} |
Identical |
GET /enrollments/{courseid}/myresources |
Removed field "ModuleId" in V1 |
GET /Enrollments/{id} |
Added new field "DateEdited" in v1.5 |
GET /enrollments/myresources |
Removed field "ModuleId" in V1 |
GET /Groups |
Identical |
GET /Groups/{id} |
Identical |
GET /InstructorLedCourses |
Added new field "DateEdited" and "CourseType" in v1.5 |
GET /InstructorLedCourses/{id} |
Added new field "DateEdited" and "CourseType" in v1.5 |
GET /instructor-led-course-sessions/{sessionid}/session-schedules/{sessionscheduleid}/attendance |
Identical |
GET /languages |
Identical |
GET /languages/{id} |
Identical |
GET /Lessons |
Identical |
GET /Lessons/{id} |
Identical |
GET /Messages/{id} |
Identical |
GET /mycatalog |
Added new field "DateEdited" in v1.5 |
GET /mycourses |
Added new field "DateEdited" in v1.5 |
GET /mydetails |
Added new field "DateEdited" and "LastLoginDate" in v1.5 |
GET /myenrollments/{courseid}?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /myenrollments?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /OnlineCourses |
Identical |
GET /OnlineCourses/{id} |
Added new field "DateEdited" and "CourseType" in v1.5 |
GET /Prerequisites |
Identical |
GET /Prerequisites/{id} |
Identical |
GET /provinces/{id} |
Identical |
GET /provinces?countryid={countryid} |
Identical |
GET /ResourceCategories |
Identical |
GET /ResourceCategories/{id} |
Identical |
GET /Resources |
Removed field "ModuleId" in V1 |
GET /Resources/{id} |
Removed field "ModuleId" in V1 |
GET /Roles |
Identical |
GET /Roles/{id} |
Identical |
GET /Sessions |
Identical |
GET /Sessions/{id} |
Identical |
GET /SessionSchedules | Identical |
GET /SessionSchedules/{id} |
Identical |
GET /Tags |
Identical |
GET /Tags/{id} |
Identical |
GET /Users/{id} |
Added new field "DateEdited" and "LastLoginDate" in v1.5 |
GET /users/{userid}/catalog |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/certificates/{id} |
Identical |
GET /users/{userid}/courses |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/enrollments/{courseid}/chapters/{chapterid}/lessons/{lessonid}?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/enrollments/{courseid}/chapters/{chapterid}/lessons?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/enrollments/{courseid}/chapters/{chapterid}?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/enrollments/{courseid}/chapters?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/enrollments/{courseid}/lessons/{lessonid}/attempts |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/enrollments/{courseid}/lessons/{lessonid}?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/enrollments/{courseid}/lessons?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/enrollments/{courseid}/resources |
Removed field "ModuleId" in V1 |
GET /users/{userid}/enrollments/{courseid}/sessions/{sessionid}?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/enrollments/{courseid}/sessions?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/enrollments/{courseid}?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/enrollments?status={status}&modifiedsince={modifiedsince}&offset={offset}&limit={limit} |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/grades/{courseid}?status={status}&modifiedsince={modifiedsince} |
Added new field "DateEdited" in v1.5 |
GET /users/{userid}/messages |
Identical |
GET /users/{userid}/messages/received |
Identical |
GET /users/{userid}/messages/sent |
Identical |
GET /users/{userid}/resources |
Removed field "ModuleId" in V1 |
GET /users/{userid}/sessiondetails |
Identical |
GET /users?Email={Email}&Username={Username}&ModifiedSince={ModifiedSince}&ExternalId={ExternalId}&Offset={Offset}&Limit={Limit} |
Added new field "DateEdited" and "LastLoginDate" in v1.5 |
GET /Venues |
Identical |
GET /Venues/{id} |
Identical |
POST /attempts/{attemptid}/finish |
Identical |
POST /Authenticate |
Identical |
POST /courses/sessiondetails |
Identical |
POST /createabsorbaccount?key={key} |
"EmailAddress" is not a mandatory field in v1.5 |
POST /createdepartment |
Identical |
POST /Curriculums/MultipleCurriculumGroups |
Identical |
POST /departments |
Identical |
POST /ecommerce/transactions/{transactionid}/status |
Identical |
POST /enroll/{courseid} |
Identical |
POST /enrollmentkeys |
Identical |
POST /enrollmentkeys/{enrollmentkeyid} |
Identical |
POST /enrollments/unenroll |
Identical |
POST /instructor-led-course-sessions/{sessionid}/session-schedules/{sessionscheduleid}/attendance |
Identical |
POST /ResourceCategories |
Identical |
POST /Tags |
Identical |
POST /users/{userid}/enrollments/{courseid}/lessons/{lessonid}/attempts/{attemptid}/finish |
Identical |
POST /users/{userid}/enrollments/{courseid}/lessons/{lessonid}/startattempt |
Identical |
POST /users/{userid}/enrollments/{courseid}/session/{sessionid}?reenroll={reenroll}&cancelsession={cancelsession} |
Identical |
POST /users/{userid}/enrollments/{courseid}?reenroll={reenroll} |
Added new field "DateEdited" in v1.5 |
POST /users/{userid}/enrollments?reenroll={reenroll} |
Identical |
POST /users/upload?key={key} |
"EmailAddress" is not a mandatory field in v1.5 |
POST /users?key={key} |
"EmailAddress" is not a mandatory field in v1.5 |
POST /Venues |
Identical |
PUT /instructor-led-course-sessions/{sessionid}/session-schedules/{sessionscheduleid}/attendance/{id} |
Identical |
PUT /ResourceCategories/{id} |
Identical |
PUT /Tags/{id} |
Identical |
PUT /Users/{id} |
Identical |
PUT /users/{userid}/user-management-settings |
Identical |
PUT /Venues/{id} |
Identical |
Appendix D
Versioned Endpoints
Below if a list of currently versioned endpoints and their differences. For all of the following GET endpoints, the API can be called with an "x-api-version" header of 1.5.0 or 1.5.1.
- /api/rest/v1_5/lessons
- /api/rest/v1_5/courses/[GUID]/certificates
- /api/rest/v1_5/tags
- /api/rest/v1_5/instructor-led-course-sessions/[GUID]/session-schedules/[GUID]/attendance
- /api/rest/v1_5/session-schedules
- /api/rest/v1_5/my-courses, /api/rest/v1_5/my-catalog,
- /api/rest/v1_5/curriculums,
- /api/rest/v1_5/course-bundles
Versioned Endpoints (x-api-version) | Response Differences |
1.5.0 | Unpaginated. Returns up to 55k rows, where possible. |
1.5.1 | Pagination. Structured matches pagination conventions. Includes wrapper with total items, limit, offset, and returned items response values. |