/
Marine LMS Web API Documentation
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). 

For example https://lms.marinels.com/help. It will redirect you to the API documentation https://lms.marinels.com/swagger/ui/index

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#

1System.Net.ServicePointManager.SecurityProtocol = System.Net.ServicePointManager.SecurityProtocol 2 | 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}

Notes: 

  • 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

Paging is mandatory via JSON object where “PageSize“ must be within configured range. Default PageSize range is [1000, 10000]

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

1{ 2 "Items": [ 3 { 4 "CourseCode": "9973469", 5 "FirstName": "Pat327", 6 "LastName": "Learner", 7 "OrganizationLoginId": "PatLearner327", 8 "UniqueLoginId": "PatLearner327", 9 "EmailAddress": "PatLearner327@marinels.com", 10 "ContactEmail": "PatLearner327@marinels.com", 11 "CourseName": "OSHA 1", 12 "CourseId": "2806f1bb-2eb5-11e7-80c0-0cc47a6f32d1", 13 "OfferingShortId": "OSHA1", 14 "RegistrationDate": "2017-07-07T22:13:49.3670000Z", 15 "RegistrationId": "bb06f1bb-2eb5-11e7-80c0-0cc47a6f32cb", 16 "RegistrationStatus": 1, 17 "OrgProfileFieldValues": [ 18 { 19 "Name": "Rank", 20 "Value": "none" 21 }, 22 { 23 "Name": "Vessel", 24 "Value": "My Vessel 1" 25 } 26 ] 27 }, 28 { 29 "CourseCode": "9973469", 30 "FirstName": "Pat326", 31 "LastName": "Learner", 32 "OrganizationLoginId": "PatLearner326", 33 "UniqueLoginId": "PatLearner326", 34 "EmailAddress": "PatLearner326@marinels.com", 35 "ContactEmail": "PatLearner326@marinels.com", 36 "CourseName": "OSHA 1", 37 "CourseId": "2806f1bb-2eb5-11e7-80c0-0cc47a6f32d1", 38 "OfferingShortId": "OSHA1", 39 "RegistrationDate": "2017-07-07T22:13:49.5370000Z", 40 "RegistrationId": "c806f1bb-2eb5-11e7-80c0-0cc47a6f32ff", 41 "RegistrationStatus": 1, 42 "OrgProfileFieldValues": [ 43 { 44 "Name": "Rank", 45 "Value": "none" 46 }, 47 { 48 "Name": "Vessel", 49 "Value": "My Vessel 2" 50 } 51 ] 52 } 53 ], 54 "TotalItems": 7502, 55 "PageSize": 2, 56 "FirstIndexOfNextPage": 2 57}

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}

Notes: 

  • 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

Paging is mandatory via JSON object where “PageSize“ must be within configured range. Default PageSize range is [1000, 10000]

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

1{ 2 "Items": [ 3 { 4 "CourseCode": "TEST", 5 "FirstName": "Pat222", 6 "LastName": "Learner", 7 "OrganizationLoginId": "PatLearner222", 8 "UniqueLoginId": "PatLearner222", 9 "EmailAddress": "PatLearner222@marinels.com", 10 "ContactEmail": "PatLearner222@marinels.com", 11 "CourseName": "Initiation A", 12 "CourseId": "a5d2a4aa-19f1-11e7-80bf-0cc47a6f32d0", 13 "OfferingShortId": "InitA", 14 "CompletionDate": "2017-04-07T23:14:48.9370000Z", 15 "RegistrationDate": "2017-04-05T18:18:28.7070000Z", 16 "RegistrationId": "aa06f1bb-2eb5-11e7-80c0-0cc47a6f32c8", 17 "RegistrationStatus": 2, 18 "OrgProfileFieldValues": [ 19 { 20 "Name": "Rank", 21 "Value": "none" 22 }, 23 { 24 "Name": "Vessel", 25 "Value": "My Vessel 1" 26 } 27 ] 28 }, 29 { 30 "CourseCode": "TEST", 31 "FirstName": "Pat143", 32 "LastName": "Learner", 33 "OrganizationLoginId": "PatLearner143", 34 "UniqueLoginId": "PatLearner143", 35 "EmailAddress": "PatLearner143@marinels.com", 36 "ContactEmail": "PatLearner143@marinels.com", 37 "CourseName": "Initiation A", 38 "CourseId": "a5d2a4aa-19f1-11e7-80bf-0cc47a6f32d0", 39 "OfferingShortId": "InitA", 40 "CompletionDate": "2017-07-02T09:48:56.3370000Z", 41 "RegistrationDate": "2017-04-06T21:07:45.1530000Z", 42 "RegistrationId": "ee06f1bb-2eb5-11e7-80c0-0cc47a6f32c1", 43 "RegistrationStatus": 2, 44 "OrgProfileFieldValues": [ 45 { 46 "Name": "Rank", 47 "Value": "none" 48 }, 49 { 50 "Name": "Vessel", 51 "Value": "My Vessel 1" 52 } 53 ] 54 } 55 ], 56 "TotalItems": 98, 57 "PageSize": 2, 58 "FirstIndexOfNextPage": 2 59}

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.

Also, to avoid missing any registrations due to processing delays on the Main system we automatically reduce the fromDate parameter by 5 min. to ensure we include any completed registration which may have been in processing during the last API call.

Example:

  • CompletionDate: 2022-07-14 21:41:44.963

  • DateModified: 2022-07-14 22:58:21.073

The DateModified is the date the registration (and exam data) has been synched and processed from Vessel to the Main system. The fromDate (-5 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.

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 (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

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 mandatory if available.

Request Body

1{"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 PageSize - usually greater than 10,000. Page sizes up to 60,000 work fairly efficient as every API call has an operational overhead.

For future releases (version TBD):

  • Please note that the PageInfo parameter will become mandatory. Therefore, it is recommended to already use PageInfo where available.

  • The PageSize must be within a configured [MinPageSize,MaxPageSize]. Default PageSize range is [1000, 10000]. If you need a PageSize outside the default range, please contact MLS support to update the PageSize configuration for your system.

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 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 TotalItems may have expressed 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) - no registrations are skipped. If you 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 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 any 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. As soon as FirstIndexOfNextPage >= TotalItems you are done paging through all items using this particular fromDate.