Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Table of Contents

API Interface Documentation

All available API endpoints incl. parameters and response data structures (except the login endpoint) can be found on your LMS url + /help (no login is required). 

...

This document only contains a selection of endpoints with detailed information on how to use them.

API Connection Protocol

Please note that all connections to the API are required to use the security protocol TLS v1.2.

Example in C#

Code Block
System.Net.ServicePointManager.SecurityProtocol = System.Net.ServicePointManager.SecurityProtocol 
                                                | System.Net.SecurityProtocolType.Tls12;

Logon (Post)

POST https://[fill-in-your-lms-domain]/services/api/oauth2/token

Body Parameters

grant_type: ‘password’
username:  org login Id or LMS user name
password: login password as clear text
scope: path to the organization like /Root/MyOrg (mandatory if org login Id is used for username)

...

Headers

Content-Type: application/x-www-form-urlencoded

...

Response

access_token is used to call into the API endpoints for authentication
refresh_token is used to refresh the access_token and extend the expiry of the token
expires_in in seconds until the access_token and refresh_token is invalid

...

Refresh Token

POST https://[fill-in-your-lms-domain]/services/api/oauth2/token

Body Parameters

grant_type: refresh_token
refresh_token: Use the refresh_token from the response during login

...

Headers

Content-Type: application/x-www-form-urlencoded

...

Response

access_token is used to call into the API endpoints for authentication
refresh_token is used to refresh the access_token and extend the expiry of the token
expires_in in seconds until the access_token and refresh_token is invalid

...

JSON Result Property Mapping

Some JSON result properties have integer values with a specific meaning. This section lists all mappings.

  • RegistrationStatus 

    • 0 = Pending

    • 1 = Active

    • 2 = Completed

  • RegistrationCreationType 

    • 0 = Unknown

    • 1 = Historical

    • 2 = Auto

    • 3 = Imported

    • 4 = Self

    • 5 = Manual

  • StudentResultAuditType 

    • 0 = ExamInstance

    • 1 = EvaluationInstance

    • 2 = ExternalGradeInstance

GetActiveRegistrationData

POST https://[fill-in-your-lms-domain]/Services/api/ExternalRegistration/GetActiveRegistrationData

URL Parameters

orgPath={orgPath}
fromDate={from date and time of format ISO 8601 in UTC (i.e. yyyy-mm-ddThh:mmZ)}
suppressCourseCodeFiltering={suppressCourseCodeFiltering}

...

  • The fromDate is the fence post date and time to return registrations with modification date > fromDate

  • suppressCourseCodeFiltering is optional and is set to ‘false’ by default. ‘false’ retains the existing behaviour.

...

Headers

Content-Type: application/json
Authorization: Bearer [access_token from the login]
Accept: application/json

Notes:
Alternative values for Content-Type and Accept: application/xml 

...

Body

Optional but recommended paging via JSON object 

...

Response

The response has a structure with the following fields:

  • Items: array of registrations. Empty [ ], if no registrations were found. Max. PageSize registrations are returned for one request. All registrations are returned if no page size has been passed in the request (not recommended)

  • TotalItems: count of all registrations found according to the request parameters

  • PageSize: page size passed in the request. 0 if no page size has been requested

  • FirstIndexOfNextPage: use this value as StartIndex to retrieve the next page. If FirstIndexOfNextPage >= TotalItems you have reached the last page. Continue to get the next page as long as FirstIndexOfNextPage < TotalItems.
    Always 0 if no pageInfo or PageSize was set for the request.

Response Data

Code Block
{
	"Items": [
		{
			"CourseCode": "9973469",
			"FirstName": "Pat327",
			"LastName": "Learner",
			"OrganizationLoginId": "PatLearner327",
			"UniqueLoginId": "PatLearner327",
			"EmailAddress": "PatLearner327@marinels.com",
			"ContactEmail": "PatLearner327@marinels.com",
			"CourseName": "OSHA 1",
			"CourseId": "2806f1bb-2eb5-11e7-80c0-0cc47a6f32d1",
			"OfferingShortId": "OSHA1",
			"RegistrationDate": "2017-07-07T22:13:49.3670000Z",
			"RegistrationId": "bb06f1bb-2eb5-11e7-80c0-0cc47a6f32cb",
			"RegistrationStatus": 1,
			"OrgProfileFieldValues": [
				{
					"Name": "Rank",
					"Value": "none"
				},
				{
					"Name": "Vessel",
					"Value": "My Vessel 1"
				}
			]
		},
		{
			"CourseCode": "9973469",
			"FirstName": "Pat326",
			"LastName": "Learner",
			"OrganizationLoginId": "PatLearner326",
			"UniqueLoginId": "PatLearner326",
			"EmailAddress": "PatLearner326@marinels.com",
			"ContactEmail": "PatLearner326@marinels.com",
			"CourseName": "OSHA 1",
			"CourseId": "2806f1bb-2eb5-11e7-80c0-0cc47a6f32d1",
			"OfferingShortId": "OSHA1",
			"RegistrationDate": "2017-07-07T22:13:49.5370000Z",
			"RegistrationId": "c806f1bb-2eb5-11e7-80c0-0cc47a6f32ff",
			"RegistrationStatus": 1,
			"OrgProfileFieldValues": [
				{
					"Name": "Rank",
					"Value": "none"
				},
				{
					"Name": "Vessel",
					"Value": "My Vessel 2"
				}
			]
		}
	],
	"TotalItems": 7502,
	"PageSize": 2,
	"FirstIndexOfNextPage": 2
}

GetCompletedRegistrationData

POST https://[fill-in-your-lms-domain]/Services/api/ExternalRegistration/GetCompletedRegistrationData

URL Parameters

orgPath={orgPath}
fromDate={from date and time of format ISO 8601 in UTC (i.e. yyyy-mm-ddThh:mmZ)}
suppressCourseCodeFiltering={suppressCourseCodeFiltering}

...

  • The fromDate is the fence post date and time to return registrations with modification date > fromDate

  • ‘suppressCourseCodeFiltering’ is optional and is set to ‘false’ by default. ‘false’ retains the existing behaviour.

...

Headers

Content-Type: application/json
Authorization: Bearer [access_token from the login]
Accept: application/json

Notes:
Alternative values for Content-Type and Accept: application/xml 

...

Body

Optional but recommended paging via JSON object 

...

Response

The response has a structure with the following fields:

  • Items: array of registrations. Empty [ ], if no registrations were found. Max. PageSize registrations are returned for one request. All registrations are returned if no page size has been passed in the request (not recommended)

  • TotalItems: count of all registrations found according to the request parameters

  • PageSize: page size passed in the request. 0 if no page size has been requested

  • FirstIndexOfNextPage: use this value as StartIndex to retrieve the next page. If FirstIndexOfNextPage >= TotalItems you have reached the last page. Continue to get the next page as long as FirstIndexOfNextPage < TotalItems.
    Always 0 if no pageInfo or PageSize was set for the request.

Response Data

Code Block
{
	"Items": [
		{
			"CourseCode": "TEST",
			"FirstName": "Pat222",
			"LastName": "Learner",
			"OrganizationLoginId": "PatLearner222",
			"UniqueLoginId": "PatLearner222",
			"EmailAddress": "PatLearner222@marinels.com",
			"ContactEmail": "PatLearner222@marinels.com",
			"CourseName": "Initiation A",
			"CourseId": "a5d2a4aa-19f1-11e7-80bf-0cc47a6f32d0",
			"OfferingShortId": "InitA",
			"CompletionDate": "2017-04-07T23:14:48.9370000Z",
			"RegistrationDate": "2017-04-05T18:18:28.7070000Z",
			"RegistrationId": "aa06f1bb-2eb5-11e7-80c0-0cc47a6f32c8",
			"RegistrationStatus": 2,
			"OrgProfileFieldValues": [
				{
					"Name": "Rank",
					"Value": "none"
				},
				{
					"Name": "Vessel",
					"Value": "My Vessel 1"
				}
			]
		},
		{
			"CourseCode": "TEST",
			"FirstName": "Pat143",
			"LastName": "Learner",
			"OrganizationLoginId": "PatLearner143",
			"UniqueLoginId": "PatLearner143",
			"EmailAddress": "PatLearner143@marinels.com",
			"ContactEmail": "PatLearner143@marinels.com",
			"CourseName": "Initiation A",
			"CourseId": "a5d2a4aa-19f1-11e7-80bf-0cc47a6f32d0",
			"OfferingShortId": "InitA",
			"CompletionDate": "2017-07-02T09:48:56.3370000Z",
			"RegistrationDate": "2017-04-06T21:07:45.1530000Z",
			"RegistrationId": "ee06f1bb-2eb5-11e7-80c0-0cc47a6f32c1",
			"RegistrationStatus": 2,
			"OrgProfileFieldValues": [
				{
					"Name": "Rank",
					"Value": "none"
				},
				{
					"Name": "Vessel",
					"Value": "My Vessel 1"
				}
			]
		}
	],
	"TotalItems": 98,
	"PageSize": 2,
	"FirstIndexOfNextPage": 2
}

Which Registrations are Included in the Response?

The registration completion date and fromDate parameter usually correlate with each other. However, if the data is coming from a vessel via sync the completion date and the time when it would show up in a GetCompletedRegistrationData call does not correlate with the passed in fromDate. The completion date of a registration is the time an exam/evaluation was completed to reflect the completion as accurately as possible (e.g. on a vessel). However, the synchronization of data from Vessel to Main and registration completion rules may take a bit of time (depending on sync interval and connectivity of the vessel and when registration processing happens on Main). If the API were only checking for completion date >= fromDate it would miss completed registrations which were synced after the fromDate but had completion dates before the fromDate - due to the sync/processing delay the completion date would be in the past. Therefore, the fromDate checks the ModifiedDate of the registration to determine whether it should get exported. That is to ensure you don't miss completed registrations which have been completed in the past but synced at a later date because the vessel was not able to sync the data in time.

...

The DateModified is the date the registration (and exam data) has been synched and processed from Vessel to the Main system. The fromDate (-5min5 min.) is compared to the DateModified and not against the CompletionDate.

Using a fromDate = 2022-07-14T23:03:30.000Z would not include this record because 2022-07-14T23:03:30.000Z minus 5 min. = 2022-07-14T22:58:30.000. The DateModified of the record is 2022-07-14 22:58:21.073 and therefore about 9sec. BEFORE the fromDate. That is why it would not include the record using a fromDate = 2022-07-14T23:03:30.000Z.

Using a fromDate = 2022-07-14T23:03:10.000Z would include the registration record: fromDate = 2022-07-14T23:03:10.000Z minus 5 min. = 2022-07-14T22:58:10.000. The DateModified of the record is 2022-07-14 22:58:21.073 and therefore AFTER the fromDate. That is why it would include the record using a fromDate = 2022-07-14T23:03:10.000Z.

A side note to be aware of for selecting the next fromDate

Calculating the fromDate for the next interval should be the date and time just before you issue the first GetCompletedRegistrationData call . This ensures (or any other API endpoint with a fromDate). This ensures that you don't miss out any registration records due to the time the API calls and processing of the retrieved data takes.

  1. currentFromDate = retrieve stored date and time from last time

  2. nextFromDate = Now()

  3. call GetCompletedRegistrationData(currentFromDate,...) and data processing

  4. On success: store nextFromDate to load next time as currentFromDate

Paging API Calls

...

Duplicate Items

As mentioned before, the fromDate is automatically reduced by 5 min. when retrieving data to make sure no items are missed. This can cause items which were returned last time to be returned again. Please make sure your code is able to handle duplicate items.

Paging API Calls

Most API endpoints support paging if the expected response can be large. The paging info is passed as request body (also see details for API endpoints) and is always recommended if available.

Request Body

Code Block
{"PageSize":60000, "StartIndex":0}

Response Object Properties

  • Items: array of registrations. Empty [ ], if no registrations were found. Max. PageSize registrations are returned for one request. All registrations are returned if no PageSize has been passed in the request (not recommended)

  • TotalItems: count of all registrations found according to the request parameters

  • PageSize: page size passed in the request. 0 if no PageSize has been requested (not recommended)

  • FirstIndexOfNextPage: use this value as StartIndex in your request to retrieve the next page. If FirstIndexOfNextPage >= TotalItems you have reached the last page. In other words, continue to get the next page as long as FirstIndexOfNextPage < TotalItems.

Note: FirstIndexOfNextPage would always be 0 if no pageInfo or PageSize was set for the request (not recommended).

...

Recommendations on

...

PageSize

It is recommended to use an appropriate page size - usually greater than 10,000. Page sizes up to 60,000 should work fairly efficient as every API call has an operational overhead.

Attention on TotalItems

The API calls are operating in a dynamic and busy environment. That is, parallel processes may update data you are about to request. Therefore, the result count returned may change while paging through the result set sets via multiple API calls. That is also true, if an API call takes multiple minutes. That means, more or less items are returned than the first TotalItems may have expressed - keeping by a previous API call. By using this dynamic approach the TotalItems is kept close to the actual state of the system - letting you retrieve the most recent data. Having said that, using the fromDate guarantees that you get all required items (i.e. completed registrations) . That is, - no registrations are omitted due to the changing environmentskipped. If you don’t did not get the registration for this round of API calls you will get them the next time.

For example, you get all active registrations - during your processing, an active registration is completed. While retrieving the first page of active registrations, you get a value for TotalItems. Because an active registration was completed in the meantime, the next call to retrieve active registrations will have TotalItems = TotalItems - 1. The same is true when for getting completed registrations - the TotalItems will be TotalItems + 1 due to the fact that a new completed registration came into the fold. That is natural in a live environment. The property FirstIndexOfNextPage will accommodate for that or you may get the completed registration the next time you retrieve completed registrationsany changed TotalItems in the most recent API response. You must not rely on the first TotalItem count and expect exactly the number of items from subsequent calls.

It is important to continue to call the API while FirstIndexOfNextPage < TotalItems to get all paged data. You must not rely on the first TotalItem count and expect exactly the number of items from subsequent callsAs soon as FirstIndexOfNextPage >= TotalItems you are done paging through all items using this particular fromDate.