Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Table of Contents
This document contains detailed descriptions how to utilize the Web API from a customer's point of view.

=================================================================================================

It is important to understand the data structure of the LMS and how the objects connected to each other.

Basic LMS objects and their relation to each other:

  • The organization path (orgPath) /Root/[org url name] identifies your top level path (i.e. to get user profile data)
  • An organization contains navigation pages (NavPages) and courses (course paths). NavPages are used to structure/group courses and other content
  • Courses contain one or more course offerings.
  • A course offering contains student registrations and instructors
  • Every assessment result (i.e. exam) is bound to a specific student registration
  • Completing a course means completing a registration. If a user retakes a course, they get re-registered and will complete a new registration

Please note that some parameters are optional and may be useful to you to filter the data you need.

The 'path' or 'course path' is an internal LMS path to identify the page or course you would like to get information from. Every path starts with /Root/[org url name] which identifies your organization in the LMS. If you navigate to a page/course in the LMS you can find the current path you are at in the browser Url. For example, the LMS path for https://orgname.marinels.com/Cnt/Root/OrgUrlName/PedestalCraneOpsManual/ is /Root/OrgUrlName/PedestalCraneOpsManual.

A list of courses and their paths can be retrieved via API endpoint Services/api/Content/GetCoursePaths?path={path}. The parameter 'path' could be that of the main organization (/Root/[org url name]) or a sub path to only get a subset of courses in your system.

Every course has at least one course offering, identified by an Id (GUID). The course offerings themselves reference the student registration objects. In order to get the course offerings of a course, you can use either API endpoint Services/api/Registration/GetCourseOfferingsByPath?coursePath={coursePath}&pageSize={pageSize}&startIndex={startIndex}&sortField={sortField}&sortDirection={sortDirection}&startDate={startDate}&endDate={endDate}&nameFilter={nameFilter} or Services/api/Registration/GetDefaultOfferingByCoursePath?coursePath={coursePath} using a coursePath from the previous call 'GetCoursePaths'. 
Most of our customers only use 1 course offering per course. If that is true for you the endpoint Services/api/Registration/GetDefaultOfferingByCoursePath?coursePath={coursePath} may be useful to get the one course offering of a course.

Once you have identified the course offerings you are interested in you can get student registrations bound to those course offerings via API endpoint Services/api/Registration/GetRegistrationsByCourseOfferingId?coursePath={coursePath}&offeringId={offeringId}&pageSize={pageSize}&startIndex={startIndex}&fromRegistrationDate={fromRegistrationDate}&toRegistrationDate={toRegistrationDate}.

FYI: We do have 2 additional API calls which return active or completed registrations across all courses or only for a subset of courses in an upcoming release (version 3.9.4).

A course offering may have instructors associated - the API call Services/api/Registration/GetInstructorsByCourseOfferingId?offeringId={offeringId} would get you the instructors of a specific course offering.

A registration may contain assessment data like exam results. To retrieve those, API endpoints Services/api/Audit/GetStudentResultsByRegistrationId?registrationId={registrationId} and Services/api/Audit/GetStudentResultsByRegistrationIds can be used, passing the registration Ids from previous API calls.

User information is returned where applicable (i.e. registration). If user information is returned as part of an object like registration only a so called 'shallow user' is returned. A shallow user object contains minimal information to identify the user. Additional API endpoints are available to get full user profile information. Use the Id (GUID) of the shallow user to retrieve a full user profile via Services/api/User/GetUser?userId={userId}, Services/api/User/GetUsersByIds?path={path} or Services/api/User/GetUsersWithOrgProfileValues?path={path} just to list a few endpoints.

Please refer to https://orgname.marinels.com/help section 'User' for all user related API endpoints.

In addition to standard user information like first name, last name, email address optional 'OrgProfileFields' (OPFs) are available and are configured on the organization. If OPFs are configured the user values for OPFs can be retrieved via endpoints 

Services/api/User/GetOrgProfileValuesForUser?userId={userId} 
Services/api/User/GetUsersWithOrgProfileValues?path={path} 
Services/api/User/GetOrgProfileValuesForCurrentUser
Please refer to https://orgname.marinels.com/help section 'User' for all user related API endpoints. 

API Interface Documentation

All available API endpoints (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#

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 

...

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#

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}

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

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}

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

...

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

...


}

GetCompletedRegistrationData

POST https://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

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:

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 (-5min.) 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 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

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:

...

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

Attention on Changing TotalItems

...

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

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

...

It is important to call the API while FirstIndexOfNextPage < TotalItems

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