API 3.0

API 3.0

Introduction

Welcome to the help area for API 3.0, the third generation of our Application Programming Interface. API 3.0 implements RESTFUL best practices whenever possible, and includes an OpenAPI 3.0.2 specification so that it can be easily integrated and used by third party tools.

API 3.0 includes a wide range of GET, POST, PATCH and DELETE operations. For example:

GET
Use cases include: (1) get a list of all courses, (2) get a list of learners in a course, (3) list the admins of a group including their associated users, (4) load details of a particular course, (5) list the players in a game, (6) get the tags associated with a certificate, and (7) get a badge including its metadata.
POST
Use cases include: (1) create a new course, (2) add a single learner to a course, (3) add a list of learners to a course, (4) add members to a group, (5) add a new user, and (6) add a sub-organization to an organization.
PATCH
Use cases include: (1) update the details of a course, (2) archive a user, (3) change the name of a user, (4) change the library that a course template is placed, and (5) change the tags associated with a user.
DELETE
Use cases include (1) delete a course, (2) delete a user, (3) delete a group, and (4) remove a learner from a course.

Reference guide

The API reference guide includes a documented list of all endpoints and operations, with descriptions, examples, schema definitions, and more.

The guide is available to administrators in the platform:
  1. Click Admin on the main navigation menu.
  2. Click API on the fly-out menu.
  3. Click API 3.0 reference guide.

OpenAPI specification

To access the OpenAPI 3.0.2 specification for our API, either click /api/v3/openapi.json or visit the API reference guide and then click "View OpenAPI spec".

General principles

API 3.0 adopts the following general principles:

RESTFUL best practices
API 3.0 follows RESTFUL best practices which include: (1) using POST for creating objects and PATCH for updating objects, (2) supporting operations such as archiving by PATCHing an 'archived' attribute rather than providing a specialized RPC-style "archive" endpoint, and (3) adopting industry-standard HTTP status codes for all our API operations.
Filter out archived items by default
Some items, such as users and courses, can be archived. By default, we filter out archived items but provide a filtering mechanism to get archived items if necessary.
Support two forms of pagination
We provide a high-performance "after" pagination style and a lower-performance "offset" pagination style that supports ordering. Generally speaking, we recommend using the "after" pagination style whenever possible, especially when iterating over large data sets.
Keep default payloads simple and small, and allow $include to pull in related data if required
We designed the API to return small, focused records with an $include option if you want to include additional optional structures. For example, if you ask for the learner (enrollment) records from a course, it doesn't automatically include their associated user records, but you can add $include=user to include those records if desired.
Don't allow $include of has-many relationships to keep pagination simple
We don't allow $include of has-many relationships so that pagination operates reliably. For example, since a course can have thousands of learners, even if we limited pagination to 50 items at a time, if each course included thousands of learners using an $include, the API call would most likely timeout. Instead, we provide specific endpoints to obtain a list of all the learners in a course.
Provide built-in support for tags and metadata
Tags and metadata are so important and common that we decided to directly include this information into item records and allow them to be easily added/updated/deleted without needing additional endpoints.
Support batch operations to perform large adds/updates asynchronously
Many operations support both synchronous and asynchronous modes. Synchronous mode allows you to add/update a single record and returns the result immediately. Asynchronous mode allows you to add/update lots of records using a batch job and returns the ID of the batch job so that you can poll for the result.
Protect the server using rate limiting
We enforce a reasonable rate limit to protect our servers from DDOS attacks. It's fairly straightforward to avoid hitting the rate limit, and we occasionally provide custom rate limits when it's absolutely required.

API keys

To make API calls using API 3.0, you must first install the API 3.0 app via the Admin/App center. Once installed, an administrator will create and configure the API keys.

API Keys For Super Administrators Only

To create a new API key, click Add and enter the name of the API key and the version of the API that you wish to use. The list of available versions will depend on which versions of the API you have installed in the app center.

The new API key is disabled by default. Once you configure the API key, click Enable to use it to invoke API calls. To see the details of an API key, click the API key name. The API key page includes details such as the secret key, usage restrictions, and usage statistics. For more details on how to manage API keys, visit Administrators/API.

Authentication

Each API key has its own secret key that is automatically generated when you add the API key via the Admin/API area. Every API call using a particular API key must include this secret API key for authentication. There are two ways that you can pass this secret key during an API call:

  • As a URL query parameter called api_key
  • As a header called x-api-key

For example, to get a list of courses, you can invoke /courses?api_key=your-key-here. If an invalid key is passed or if the key is associated with an API key that is not currently enabled, an exception is raised.

Special query-string parameters

All GET operations can support a set of special query-string parameters. The specific values for an operation are listed in the QUERY-STRING PARAMETERS area of the operation in the API reference guide. Below is a summary of the special parameters, and subsequent sections provide details.

$after
Provide $after=xxx, to return items whose ID is larger than xxx, ordered in ascending order of ID. 
$count
When $count is set to true in the query string, a GET for a list of items will return the number of items instead.
$filter
Filter lists by providing a JSON-format set of filter conditions.
$include
Use $include to include one or more associations (separated by commas) along with the item data. 
$limit
A GET for a list of items will return the first 10 items by default. You can override this by setting $limit=count where count can be up to 100.
$offset
Provide $offset=xxx, it will skip the first xxx items when returning a list.
$options
Some operations support options via the $options parameter as a JSON-encoded structure.
$order
Specify one or more fields to order a returned list, separated by commas. 

Filtering

Filter lists based on their attributes using a combination of the standard operators "and", "or", "not", "eq", "neq", "gt", "gte", "lt", "lte", "in", and "contains". To specify a filter, set the $filter parameter to a JSON structure representing the filter. If an attribute is part of an item's metadata, precede it with the prefix "metadata". For example:

/course/ID/learners?$filter={"completed": true}
Returns learners who completed the specified course.
/course/ID/learners?$filter={"completed": false}
Returns learners who have not completed the specified course.
/course/ID/learners?$filter={"gte": {"completed_at", "2020-07-10"}}
Returns learners who completed the specified course after the specified date.
/course/ID/learners?$filter={"and": [{"gte": {"completed_at", "2020-07-10"}}, {"lte": {"completed_at": "2022-07-10"}}]}
Returns learners who completed the specified course between the two specified dates.
/courses?$filter={"in": {"course_code": ["007", "009"]}}
Returns courses whose course code is either "007" or "009".
/certificates?$filter={"gt": {"metadata.created_at": "2020-07-10"}}
Returns certificates that were created after 2020-07-10.
/users?$filter={"contains": {"roles": ["Learner", "Instructor"]}}
Returns users who are either a Learner and/or an Instructor.

Generally speaking, a JSON condition has a single key/value pair. Below are the conditions that we support.

PLEASE NOTE: <simple attribute> refers to an attribute that is a primitive such as an integer or string, and <array attribute> refers to an attribute that is an array of primitives (such as "tags" and "roles"):

{"and": <expressions>}
where <expressions> is an array of JSON conditions, returns true if all the expressions are true
{"or": <expressions>}
where <expressions> is an array of JSON conditions, returns true if at least one expression is false
{"not": <expression>}
where <expression> is a JSON condition, returns true if the expression is false
{"eq": {"<simple attribute>": value}}
a structure of the form {"<simple-attribute>": value} where value can also be null, returns true if the attribute has the same value
{"neq": {"<simple attribute>": value}}
a structure of the form {"<simple-attribute>": value} where value can also be null, returns true if the attribute has a different value
{"gt": {"<simple attribute>": value}}
a structure of the form {"<simple-attribute>": value}, returns true if the attribute is greater than the value
{"gte": {"<simple attribute>": value}}
a structure of the form {"<simple-attribute>": value}, returns true if the attribute is greater than or equal to the value
{"lt": {"<simple attribute>": value}}
a structure of the form {"<simple-attribute>": value}, returns true if the attribute is less than the value
{"lte": {"<simple attribute>": value}}
a structure of the form {"<simple-attribute>": value}, returns true if the attribute is less than or equal to the value
{"in": {"<simple attribute>", <values>}}
where <values> is an array of values, returns true if the attribute is equal to one of the values
{"contains": {"<array attribute>": <values>}}
where <values> is an array of values, returns true if the attribute contains at least one of the values

We also provide a shortcut for simple cases:

{"<simple attribute>": value}
equivalent to "eq"; return true if the attribute is equal to the value
{"<simple attribute>": <values>}
equivalent to "in"; return true if the attribute is equal to one of the values
{"<array attribute>": <values>}
equivalent to "contains"; return true if the attribute attribute contains at least one of the values

Please note that the "contains" operator can only be applied to attributes that represent an array of items. Currently, only a user's "roles" and an items "tags" return an array, so you cannot use "contains" on any other attributes.

Pagination

We provide a high-performance "after" pagination style and a lower-performance "offset" pagination style that supports ordering. Generally speaking, we recommend using the "after" pagination style whenever possible, especially when iterating over large data sets.

$limit

A GET for a list of items will return the first 10 items by default. You can override this by setting $limit=count where the list of items can be up to 100. To get items beyond the first 100 matches, use either the $offset or $after parameters. For example:

/courses?$limit=50
Return a list of the first 50 courses.

$after

When using $after=xxx, it returns items whose ID is larger than xxx, ordered in ascending order of ID. To paginate efficiently through large data sets, start by getting the first set of items without using the $after parameter, then call GET again and provide the ID of the last item returned using $after. Repeat these steps until no more items are returned. Paginating using $after is faster and more efficient than using $offset, but you cannot use $order in conjunction with $after. For example:

/courses?$after=16678
Returns the next 10 courses whose IDs are larger than 11678.

$offset

If you provide $offset=xxx, it will skip the first xxx items when returning a list. You can paginate through smaller datasets using a combination of $offset and $limit by looping through calls to GET and incrementing the offset by $limit after each call until no more items are returned. You can use $order in conjunction with $offset. Note that paging through datasets with more than 1000 items can become very slow using this approach; it is much more efficient to paginate through large datasets using $after. For example:

/courses?$offset=50&$limit=20
Returns the courses with positions 51..70.

Includes

Use $include to include one or more associations (separated by commas) along with the item data. This option can reduce the number of API calls required to get the data you need. For example:

/courses/123/learners?$include=user
Return a list of learner records for  the specified course along with the user associated with the learner record.

If an $included association is called xxx then the value of xxx is provided as a field called xxx in the item. In the example, the returned items would be of the form {id: <course id>, name: <course name>, ...., user: {id: <user id>, first_name: <user first name> ...}}

Ordering

Specify one or more fields to order a returned list, separated by commas. By default, items are ordered using the specified field in ascending order, however you can add a ":" followed by "asc" or "desc" to specify ascending or descending order, respectively. If no order is specified then the default ordering is "id:asc". For example:

/courses?$order=name:desc 
Return courses ordered in descending order of name.

Counting

Include $count=true in the query string of a list operation, to return the number of matching items in the format {count: <number>} instead of the items itself. For example:

/courses/ID/learners?$count = true
Returns the number of learners in the specified course
/courses/ID/learners?$filter={"completed": true}&$count=true
Returns the number of learners that have completed the specified course

Transactions

API 3.0 automatically wraps certain operations in transactions. Specifically:

  • A DELETE operation
  • A POST operation that creates a single object
  • Each individual object creation that occurs in a batch job associated with a batch POST operation
  • A PATCH operation that updates a single object
  • Each individual object update that occurs in a batch job associated with a batch PATCH operation

Tags

Many objects support tags, and these tags are represented as a simple attribute called "tags" that is an array of strings. You can get/set tags like any other attribute of an object. To add or remove tags to an existing set of tags, first use a GET to fetch the existing tags, modify them accordingly, and use a PATCH to update the object to the new set of tags.

Metadata

Objects that appear in the resources catalog such as Course templates and Job titles have metadata that contains the fields listed below. This metadata is represented as an attribute called "metadata" with those specified fields. When you create or update an object that has metadata, you can also provide the language, scope, and scope_id metadata fields.

creator_id
The ID of the user that created the resource. If you create a resource using the API, this is automatically set to the user associated with the API key.
created_at
The time that the resource was created.
language
The language of the resource, spelled in the English language. If you create a resource using the API and do not explicitly specify a language, it's automatically set to the language of the user associated with the API key.
scope
The scope of the resource: "Business", "Organization", or "User". This value corresponds to the library setting in the resources catalog associated with the resource. If a scope is not provided when creating a resource, it's set to "Business" by default.
scope_id
This value indicates the LMS ID associated with the scope. If the scope is "Business", it is the ID of the creator's business. If the scope is "Organization", it is the ID of the specific organization. If the scope is "User", it is the ID of the specific user. If the scope_id is omitted, it's set automatically depending on the scope: "Business" => the creator's business ID, "Organization" => creator's organization ID, "User" => the creator's ID.

Batch operations

API 3.0 supports asynchronous creation and update of objects using its batch mode feature. Endpoints for batch operations always end in /batch. The maximum number of objects that can be created or updated varies by operation and is documented in the API reference guide. 

    Batch create

    When using batch create, the payload is an array of structures where each structure contains the attributes of an object to create.

    Batch update

    When using a bulk update, the payload is an array of structures where each structure has two top-level fields:

    id
    The ID of the object to update
    attributes
    The attributes of the object to update

    Batch response

    When you execute a batch create or update operation, the server immediately returns a JSON structure with a single field called "batch_id" which is the ID of a batch object that executes the batch operation on a job server in the background. The client can then invoke /batches/ID to get the batch object corresponding to the operation. The most important fields of a batch object are:

    status
    This indicates the current status, which is either Queued, Running, or Finished. "Queued" means that the batch is ready to run but has not started to run yet. Note that "Finished" means that the batch has been run, but does not indicate if an error occurred or not. The response_bodies must be inspected to determine the status of each operation in the batch.
    processed
    This indicates the number of operations processed so far. For example, if you ask that 100 users are created, then this number will show the number of users created so far.
    results
    This is an array of structures for each of the bulk operations, each of the format {object: <object>, status_code: <HTTP numeric status code>, status_message: <HTTP status in a readable form>}. If the operation was a batch create then <object> is the object that was created, and if the operation was a batch update then <object> is the object after the update was performed.

    Using batches

    Performing a batch operation is normally implemented as:

    1. Invoke the batch create/update operation, which returns the ID of the batch object.
    2. Enter a loop that polls every few seconds and does a GET on /batches/<batch id>. Exit the loop when the status is "Finished".
    3. Look at the response_bodies array to see the status and return value of each operation.

    Status codes and error handling

    Every return result from an API call has an associated HTTP status code. If the result represents an error, we also include a single "message" element in the JSON that describes the error. 

    Below is a list of all the possible HTTP status codes and what each means:

    200 (OK)
    This is returned from successful retrieval operations.
    201 (Created)
    This is returned when you successfully create a new object, such as creating a group or adding a user to a group as an admin.
    202 (Accepted)
    This is returned if you add an object to a collection and the object is already a member of the collection. For example, adding a user as a group admin when they are already an admin of the group. It's also returned when you start a bulk operation.
    204 (No Content)
    This is returned when an object is successfully deleted.
    207 (Multi-Status)
    This is returned when you perform a bulk operation. It's also the overall status of a batch operation. In both cases, you must review the results to see the status for each individual operation.
    400 (Bad Request)
    This returned when there's something semantically wrong with the query, such as filtering on an attribute that doesn't exist or trying to overwrite a read-only attribute.
    403 (Forbidden)
    This is returned when the API key does not have permission to perform an operation.
    403 (Not Found)
    This is returned when a URL cannot be resolved due to either a missing object, a missing association, or a missing endpoint.
    405 (Method Not Allowed)
    This is returned if the URL does not support the particular HTTP method. For example, attempting to DELETE an endpoint or PATCH to a read-only object.
    409 (Conflict)
    This is returned if the operation could not be completed because of a constraint. For example, attempting to DELETE a job title when users already have that job title.
    429 (Too Many Requests)
    This is returned if you exceed the rate limit.

    Replica lag

    Our platform uses a database replica when processing heavy read-only tasks such as generating reports and data for dashboard widgets. To ensure that large numbers of GET List operations do not impact our primary database, we also use the database replica when loading lists of items for the API. A database replica can sometimes lag behind the primary database by up to a second, so please be aware that if you add an item and then immediately perform a GET List that would normally include that item, there is a small window of time where the item might not be present in the list. Note that a GET of a single item does not use the database replica, only a GET List operation.

    Rate limiting

    If you exceed 120 API calls per minute, you will receive a rate limit exception (HTTP status 429), and API access will be disabled for a minute. This is to protect our servers against API attacks. If you have a special reason why your code needs to exceed this rate limit, then please contact our support team to request an increase.

    Known issues and limitations

    Coming soon

    Additional endpoints coming soon

    Our goal is to add additional operations to API 3 by the end of Q2 2024 so that it provides all the functionality available in API 1. At that point we will announce our intention to deprecate API 1 by the end of 2024.

    The differences between API 2.0 and API 3.0

    API 3.0 has several benefits over API 2.0:

    More RESTFUL design
    It avoids use of RPC-like operations such as "archive" in favor of using a PATCH operation to change a class's attribute "archived" to true.
    Improved consistency
    There are a smaller number of endpoint operations which consistently provide the core data of an object with the option to load additional related information using $include.
    Built-in support for tags and metadata
    For objects that support tags and metadata, you can easily set and get this information. No additional endpoints are necessary, tags and metadata are now represented as attributes.
    Improved filtering
    Built-in support for complex filtering conditions.
    Clean separation of options from core payload
    Options such as whether to send a notification when adding a new learner are now provided via a separate $options parameter.
    Consistent use of relationship objects
    For example, if you ask a course for its learners, it returns Learner objects that represent their enrollment in the course, with the option to $include the associated User if you want it.
    Improved batch operations
    Batch operations are now simpler to invoke, and each has a separate .../batch endpoint. This also simplifies the OpenAPI spec since a single endpoint no longer has to support two different modes of operation.
    Support for organization placement
    When you add/update organizations, you can specify their placement in the hierarchy.
    Support for delete operations
    You can now delete most kinds of objects.
    Better performance
    The back-end code has been heavily optimized, and in some cases is 100x the speed of API 2.0.
    A new $count parameter
    You can add $count to a list operation to return the count of items instead of the actual items.
    More endpoints
    We've added support for important concepts such as job titles and game history, with a lot more endpoints to be released soon.
    Improved documentation
    The API reference guide includes a lot more help and detail about operations, attributes, and schemas.

    Migrating from API 2.0 to API 3.0

    • Administrators:

      • Review the API v3 documentation.

      • Install API 3.0 in the CYPHER App Center.

      • In the Admin Console, navigate to the API tab.

      • Generate a new API key by clicking + Add in the top right corner.

      • Select version 3.0 and name the API Key something indicative of where it will be used.

      • Click the enabled checkbox for the new API key on the far right of the screen.

      • Click the name of the new API key.

      • Set restrictions to match your current production API key configuration.

      • Securely share the Secret Key with the team members who will be updating the API calls.

    • Developers:

      • Review the API v3 documentation.

      • Identify all internal applications and services that use API v2.

      • Update those applications and services to use API v3, including endpoint changes, data structure modifications, and authentication updates.

      • Conduct thorough testing in a development/staging environment.

      • Coordinate deployment with the infrastructure team.

    • Infrastructure Teams:

      • Prepare the infrastructure for API v3 deployment.

      • Ensure proper monitoring and logging of API v3 usage.

      • Assist development teams with deployment and troubleshooting.

    • Data Teams:

      • Update any internal data pipelines that utilize the old API.

      • Ensure data integrity during the migration.



      • Related Articles

      • PHP

        Overview Our open source PHP client provides a simple way to invoke the API from PHP. To download the PHP client, right-click /clients/lms_api.php and then save the contents to your local computer. Example: code The following example lists all the ...
      • Javascript

        Overview Our open source Javascript client provides a simple way to invoke the API from Javascript. To download the Javascript client, right-click /javascripts/lms_api.js and the save the contents to your local computer. The Javascript client is ...
      • Java

        Overview Our open source Java client provides a simple way to invoke the API from Java. Installation You can download the Java API from/clients/java-api-client.zip and include it in your project. Dependencies: http://unirest.io/java.html and ...
      • NET

        Overview Our open source C# client provides a simple way to invoke the API from C#. Installation You can download the C# API from /clients/csharp-api-client.zip and include it in your project. Depencencies: https://github.com/mgholam/fastJSON ...
      • Ruby

        Our open source Ruby client provides a simple way to invoke the API from Ruby. Overview To download the Ruby client, right-click /clients/lms_api.rb and the save the contents to your local computer. Example: code The following example lists all the ...