The Same Origin Policy

For security reasons our API does not accept calls to it directly from web pages hosted in a different domain. This is called “The Same Origin Policy” and we disallow it for security reasons.

For example, you won’t be able to call our API directly from JavaScript. In the very unlikely event that you are integrating via web pages, then you need to channel your web pages to your own hosted server-side application. Your server-side application will then be responsible to call our API and in turn respond to your web pages. This is commonly known as a proxy server application as all it is doing is channelling the calls to a different domain.

Most commonly, however, you will be integrating directly from your server-side (back-end) application/processes and no front-end web pages will be involved in the process.

Content Type

This API supports only JSON format.

Whenever the API responds with some content, the response will be in JSON and include the following response header.

Response Headers

Content-Type: application/json; charset=utf-8

Hence when calling the API you don’t need to include an specific Accept header as any Accept header value will be ignored and always JSON returned.

When sending/posting data in the body of the request via POST or PUT to the API (with the exception of getting an access token), you must always include the following request header.

Request Headers

Content-Type: application/json

Error Response

If you don’t set the content-type to json when posting data then you’ll get a 415 Unsupported Media Type HTTP status code back with JSON content such as:
{
	"Message": "The request entity's media type 'application/xml' is not supported for this resource.",
	"ExceptionMessage": "No MediaTypeFormatter…",
	"ExceptionType": "System.Net.Http.UnsupportedMediaTypeException",
	"StackTrace": "   at…"
}
For readability, the JSON is shown indented and text truncated.

Authentication and Authorisation

With the exception of the endpoints under Diagnostics, all the other endpoints require a valid authenticated token to be supplied with each API call. In addition once authenticated each account has to be authorised to call the API endpoint. When an account is set up by our Support Staff, they will tell you the list of API endpoints that you are authorised to call. Calling other endpoints will result in an unauthorized error.

The tokens generated by our API are OAuth Bearer tokens and have limited expiry time and cannot be refreshed.

To get an Access Token

Do a POST request to the api/token endpoint with the following request header and payload.

Request Headers

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

Payload

grant_type=password&username=myclientusername&password=MyClientPassword
Where myclientusername and MyClientPassword are those credentials supplied to you by our Support Staff. Noting that the username is case-insensitive but the password is case-sensitive.

Response

If the credentials are ok you’ll get a 200 OK HTTP status code back with JSON content such as:
{
	"access_token": "1zwcsRbJGnsrBxYl…",
	"token_type": "bearer",
	"expires_in": 14399
}
For readability, the JSON is shown indented and the access token truncated (which is quite long). The expires_in field indicates the number of seconds before the token it expires.

Error Response

If your credentials are incorrect or account is disabled then you’ll get a 400 Bad Request HTTP status code back with JSON content such as:
{
	"error": "invalid_grant",
	"error_description": "The user name, password or account is invalid."
}

To send an Authenticated Request

Whenever calling any of the endpoints (except Diagnostics) you need to supply the access token in the request header as follows.

Request Headers

Authorization: Bearer 1zwcsRbJGnsrBxYl…
As shown above set the value of the Authorization header to Bearer followed by a space and then followed by the access token which is the “access_token” filed value that was retrieved via the api/token call.

Response

If the token is valid, not expired and authorised to call the endpoint then you simply get the response as described in the corresponding API endpoint documentation.

Error Response

If the token is invalid (e.g. corrupt, tampered), expired or not authorised to call the endpoint then you’ll get a 401 Unauthorized HTTP status code back with JSON content such as:
{
	"Message": "Authorization has been denied for this request."
}

How to use the Access Tokens

Because the access tokens are set to expire after a pre-determined time (currently 4 hours but subject to change without notification), the recommended approach to use a token is as follows:

  1. Start your session.
  2. Get a new access token.
  3. Store your token internally in the server memory for subsequent calls.
  4. Call an API endpoint passing the access token.
  5. If a 401 HTTP status is returned, then it is possible the token has expired. So get a new access token and store it in memory and try calling the same API endpoint again. If it fails again then escalate the error and stop your session as your token is not expired and possibly has no permissions.
  6. Repeat from step 4 for the next API endpoint.
  7. End your session.

Response Envelopes

All responses except Diagnostics endpoints in our API that return a DTO (Data Transfer Object) or a collection of DTOs share a common response JSON format where the response data is wrapped in an envelope object.

There are two types of envelopes, one that wraps a collection of DTOs and one that wraps a single DTO.

Regardless of the envelope please note that in the API endpoint documentation the resource description and examples under the endpoint documentation always show single DTO representation. However depending on the API endpoint you called, in practice, you will either get an envelope that wraps that kind of DTO or get an envelope that wraps a collection of those DTOs. In example, the API endpoint description and examples show a single DTO representation returned rather than the actual envelope description and examples. This allows you to clearly see the actual important resource data representation without getting bogged down with the same repeated envelope description and examples.

Envelope Wrapping a Collection of DTOs

When the API documentation says that returns various objects (e.g. get all students), then the API will return an envelope that wraps a collection of student DTOs. Below are the resource descriptions for DtosEnvelope and DtosMeta.

DtosEnvelope
NameDescriptionTypeAdditional information
Data

The actual resource data being returned, a collection of DTOs.

Collection of Object Required
Meta

The meta-data for the collection response.

DtosMeta Required
DtosMeta - The Offset, Limit, HasMore and TotalCount fields are only not-null if the rows returned are limited by the corresponding API endpoint.
NameDescriptionTypeAdditional information
Offset

When not null indicates the number of rows offset on the returned dataset.

integer

None.

Limit

When not null indicates the limit of rows returned from the dataset.

integer

None.

HasMore

When not null indicates if there are more rows than the ones returned due to enforcing the above row Limit and Offset.

boolean

None.

TotalCount

It is only not null when client explicitly asked to return total count of rows. Getting this count is an extra read operation and hence ideally should not be requested and instead client should rely on the HasMore field.

integer

None.

The JSON content of the envelope returned will look like:

{
  "Data": [
    { ... },
    { ... }
  ],
  "Meta": {
    "Offset": 0,
    "Limit": 3,
    "HasMore": true,
    "TotalCount": 6
  }
}

Envelope Wrapping a Single DTO

When the API documentation says that returns a single object (e.g. get a student by id), then the API will return an envelope that wraps a single student DTO. Below are the resource descriptions for DtoEnvelope and DtoMeta.

DtoEnvelope
NameDescriptionTypeAdditional information
Data

The actual resource data being returned, a single DTO.

Object Required
Meta

The meta-data for the response.

DtoMeta Required
DtoMeta - It is currently empty and reserved for possible future use and to maintain consistency with envelopes used for a collection of DTOs.

The JSON content of the envelope returned will look like:

{
  "Data": { ... },
  "Meta": {}
}

POST and PUT requests

The same DTOs returned during GET requests are used for creating (POST) and updating (PUT) the record in the QS Enrolment Solutions database.

Our API doesn't support partial updates of a record. Hence when updating ensure that you pass the existing values in addition to those that you are changing.

Unless otherwise specified in the API Help documentation, all DTO fields need to be passed when PUT requests are sent to the API. During an update, the API will replace all existing fields in the database with those specified in the body of the request. If a field is not specified in the body of the request then usually a null value will be written to the corresponding field in the database. The only exception are those DTO fields that are ignored during PUT requests sent to the API. Refer to the API endpoint documentation to find out which fields are ignored.

Rate Limiting

In order to prevent abuse our API implements a number of throttles.

Every client is subject to an IP and endpoint concurrent request throttle. This means that if you hit a specific endpoint from the same IP repeatedly then this counts towards the request throttle for that IP and endpoint combination. If you hit from the same IP another endpoint, then that IP and endpoint combination will have its own throttle count.

Throttling is done based on different time periods: per second, per minute, per hour, per day and per week. Each of these has its own count, and if either of them reaches the threshold then you request will be rejected/dropped. Just for illustration purposes, the default threshold counts for these periods is shown below, however the count is subject to change and each IP and/or endpoint might have a different threshold count imposed by us.

  • Per Second: 10
  • Per Minute: 120
  • Per Hour: 3600
  • Per Day: 43200
  • Per Week: 216000

If you exceed the throttle threshold, you’ll get a 429 HTTP status code back with no content. HTTP status code reason will be set according to the time period that reached the threshold. For example:

If the per-second threshold was hit, then the HTTP status code will be such as:
429 API calls quota exceeded! maximum admitted 10 per Second.
If the per-minute threshold was hit, then the HTTP status code will be such as:
429 API calls quota exceeded! maximum admitted 120 per Minute.
Of course, the above numbers (10, 120) will depend on the actual threshold count imposed for your IP and endpoint combination.

Apart from the HTTP status code 429 and the reason returned it will also include the following response header.

Response Headers

Retry-After: 33
The above number will tell you the number of seconds left before your count will be reset and thus you’ll be allowed to make a new successful call.

Examples

Before starting to write your server-side code we recommend that you first use a simple REST client tool to do some initial testing of the endpoints that you’ll be using in your integration project.

In the examples below we have used Chrome’s “Advanced REST client” extension.

Example 1 – POST credentials to get an access token

REST Call - Example 1

Example 2 – GET a collection of resources limited to a set number of rows

REST Call - Example 2

Example 3 – GET a single resource by id

REST Call - Example 3

Example 4 – POST (create) a new resource

REST Call - Example 4

Note that that the request header conten-type has been set to application/json, JSON data passed in the body of the request and the response header provides the location of the just created resource.

Example 5 – PUT (update) a resource by id

REST Call - Example 5

Example 6 – DELETE a resource by id

REST Call - Example 6