Developer Platform (April 2024)

Brightspace API calling conventions

«  Typographical conventions for this reference   ·  [   home  ·   reference  ·   community   |   search  ·   index   ·  routing table   ·  scopes table   ]   ·  API versions (working to a common version)  »

Brightspace APIs provide a REST-like interface to third-parties for interaction over HTTP with back-end Learning Service providers. This topic covers the basic calling conventions for the API: how to make requests of the API and what to expect back from the service in response.

In general, the Brightspace APIs are constructed with the philosophy of expecting that clients be “forgiving about what they receive, and strict about what they send”.

Action requests

The API comprises a number of actions that clients can take to create, modify, retrieve, or delete resources employed by an LMS. Each action consists of an HTTP URL, or route, and a possible set of parameters.

You can use the catalogue of actions in and of themselves. To do so, you will need to be able to assemble the appropriate HTTP requests, and data structures, to send to the service, and decompose the HTTP responses (and data structures) that the service sends back.

You might also choose to use a library that abstracts away the individual routes so you can make calls to the API at a higher level without dealing with the mechanics of manipulating URLs directly.

Connection security

Because the Brightspace APIs use exactly the same communications channel as the browser web-interface uses, the guidelines for how to connect to the APIs have the same technical requirements as using the web user interface with a supported browser. API calls must be made over HTTPS, and use the same level of transport security as required by common access to Brightspace.

Structure

An action consists of an HTTP method (most commonly GET, POST, or DELETE). The general structure of the URL provided with this method looks like this:

https://targethost/d2l/api/component/version/path

targethost

The host name for the LMS deployment. This will typically be set by the organization that your application is connecting to.

component

A short string identifying an LMS product component.

version

API version supported by the product component. As a routine practice, all clients of the API should verify the version of the service they’re connecting to. If actions fail because of a version mismatch, or lack of support from the service, verifying the supported version can help you provide better disposition information to the user. See the API versions topic for more information.

path

Path to the particular action. This will have static structural information about the kind of action you’re taking, but may also include parameterized data like entity identifiers, or action types (such as add, remove, and so on).

Example. If you make an HTTP GET call to https://devcop.brightspace.com/d2l/api/lp/1.0/users/whoami, you are taking the action to retrieve the user information for the current user context (i.e. the person your application is “logged in to the LMS as”). The service will send you back a block of JSON data looking something like this:

{
  "Identifier": "2046",
  "FirstName": "Jodi",
  "LastName": "Singh",
  "UniqueName": "jodi.singh",
  "ProfileIdentifier": "ixCgsUKx1I"
}

Authorization parameters

Brightspace APIs use the industry-standard OAuth2 Authorization Code Grant workflow to establish an authenticated application and user context for actions. Because of the stateless nature of REST-like APIs, after your client application successfully retrieves an access token via this workflow, you must provide the access token in the request headers for every API request, as per standard OAuth2 authorization.

Example. Here’s what a fleshed out API request might look like for a sample action (note that we’ve put line-breaks in this sample for readability purposes only; in a real request, the Authorization header value must contain no line-breaks):

GET https://someLMShost.edu/d2l/api/lp/1.31/users/whoami HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjJjYWNjMzlhLWMwOGQ
tNDA2Yy1iN2MxLTc1YmQyMTI5M2I5NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE2NDQ0MjE5
MzAsImV4cCI6MTY0NDQyNTUzMCwiaXNzIjoiaHR0cHM6Ly9hcGkuYnJpZ2h0c3BhY2UuY2
9tL2F1dGgiLCJhdWQiOiJodHRwczovL2FwaS5icmlnaHRzcGFjZS5jb20vYXV0aC90b2tl
biIsInN1YiI6IjMxNDAzIiwidGVuYW50aWQiOiI2MzViOWVjYy1hOTczLTQ4ZDMtYjVkNi
0wN2QyNWEzNDY1MjEiLCJhenAiOiIyMDNhMDc0Ni03NzY5LTQ4YjMtOTVhZS03YTQ2NDBi
Y2QyNjciLCJzY29wZSI6ImNvcmU6KjoqIiwianRpIjoiZDJmNGEzN2YtM2M3MC00M2I1LW
JkNzItNTVmNGJjYzQwZDE3In0.C70z9qRj0Tm-iVKvpfrrvIG8FibD3w9NUWbHAxJL74_4
8rjdP73AKUyWAjuR4ohZNvBQqJUfsh_FBMQPckubF-TF9E6ECfjdrTNHHI-OqEBFYXeTyx
kJH9_D7Bch_p1CuSM7G6E9Cl4rqQsA92_1qRYt01ljey9gbBxX1WTV5ofXwtmp8GZ83247
F1Ps-7hZ302umUQ-VXX2K5NGF-nkr1x6lN1SIp-dx_5peUnGiyRH2K6K6ZRn3kPihmsMXh
uUklXmm8vwNN_hyNffzHiviHmbXeHS1qG5tNlKDhYY4tylrRHpttJfznN4YqlpYqM8iGlR
Ale4BOGUW2-Zr9YDOg

Brightspace also continues to support a more complex, legacy authentication system: ID-Key Auth. We recommend that your applications use OAuth2, but for more information on the legacy authentication system, refer to our topic on the ID-Key Authentication workflow.

Resource parameters

Many actions in the API require parameters that identify the principle resource involved. In many cases, you do this by putting the relevant system identifier into the action’s route itself. For example, here’s an action requesting user information for the user with ID 3643 (note that like most examples in this reference, we leave out the authorization parameters for clarity):

https://devcop.brightspace.com/d2l/api/lp/1.0/users/3643

In these cases, be sure to provide a validly formed parameter value in the route, as the service can misinterpret the action you’re trying to take if you provide an invalid parameter format (for example, providing a system identifier value that has non-numeric characters in it).

Result control parameters

Some actions require you to provide information to control the value(s) the service should return. For example, you might want to select a subset of users in a search operation (because requesting the entire list of users might fetch back a substantial amount of data to no good purpose). Often in these cases, you can provide query parameters to help the service return more precisely needed results, for example, this action to find all teaching assistants in a department:

https://devcop.brightspace.com/api/lp/1.0/enrollments/orgUnits/orgUnitId/users/?roleId=roleId

You provide the org unit ID for the department, and provide the system role ID that your service has assigned to teaching assistant roles.

Unless otherwise noted, invalid query parameters may be ignored. For example, if you provide a value that is not a system D2LID type when one is required (or the one you provide is not correctly formatted), your query parameter will typically be ignored.

Request body

Some actions require you to provide richer data than merely a query parameter; for example, some actions require you to send a block of service-specific data as might be encapsulated in an object.

Provide appropriately typed content headers. In the cases where you must provide a serialized JSON data block as part of your request, you should ensure to set the appropriate Content-Type header to application/json:

  • If your request is a single part request, then you should set the HTTP request’s Content-Type header to application/json

  • If your request uses a multi-part body, then you should set the HTTP request’s content header to the appropriate multi-part type (see the file upload topic for some examples), and the Content-Type for the body part containing JSON to application/json.

In the request body parts where you must provide data other than a JSON structure, you should make sure to set the appropriate content type header to a type that matches the content you’re providing.

Provide complete structures. Note that when the service expects you to send a JSON structure in a POST or PUT request body, it may expect all the properties to be present, even if the contents of the property are empty (that is, a string property with a value of “”), or null. It is generally safer to have calling code that provides a completely filled out JSON structure (with some “blank values”), rather than passing in only the fields that the caller thinks are “relevant”.

Some actions accept data structures other than serialized objects (for example, uploaded files).

Request body size limits. When you upload JSON data, you may not send more than a 1 MB block of data (whether you send the JSON by itself in a POST request, or in combination with a binary upload).

When you upload binary data (such as a file upload), you may not send more than 490 MB in a single upload.

Rate limiting

Brightspace back-end services implement rate limiting functionality to protect the service’s availability in the face of excessive API call rates from misbehaving API clients.

The system implements a token-bucket credit scheme. Each API call the caller makes will remove a number of credits from the caller’s bucket of available credits. When the caller no longer has enough credits in its bucket to pay for its API calls, the service will respond with a 429 status code.

To support API callers adapting their behaviour around limiting the rate of their calls, all API responses contain three special headers providing information about the caller’s available credits:

X-Rate-Limit-Remaining

Number of credits remaining in the caller’s bucket after this call.

X-Request-Cost

Number of credits that the just-called API request cost the caller. D2L reserves the right to dynamically adjust the credit cost for any particular API route at its discretion.

X-Rate-Limit-Reset

Number of seconds until the caller’s credit bucket gets fully replenished by the back-end service.

If the API responses do not contain these headers, then the back-end service has not yet had the rate-limiting scheme activated. For more information about when your particular back-end service will have this scheme activated, please consult your D2L approved support contact.

API responses

The service responds to API actions along two general information paths: HTTP return codes provide you with information about the disposition of your request, and JSON (RFC 7159) data returned to you encode resources resulting from your request. In general, we use the term a JSON data block to refer to a JSON document: we use the term block to reinforce that our APIs may return JSON arrays, objects, or in some cases simple JSON native data types (like JSON strings, numbers, true, false, or null).

In this reference, rather than provide a precise schema for the various JSON data blocks, we provide you with a “looks-like” sample that uses several conventions you should remember.

Returned resources

The service returns resources to a client caller via JSON-formatted serialized data. For details on how this API reference describes this data, see our JSON data presentation conventions. Notice that, because JSON supports only a small number of generic field data types, the Brightspace APIs make particular use of some of those generic types, expecting to parse/format them in a particular way.

One notable example is a date-time value. In most cases, when a date-time value appears in a JSON block, it is formatted as a UTCDateTime value (a string that you should treat as formatted according to the ISO 8601 standard). In some cases, the API encodes date-time values in JSON as a UnixTimestamp (a number value counting the number of seconds since the Unix epoch). In all cases, date-time values are expressed with a UTC+0 timezone offset.

Be tolerant in parsing results. In general, you should parse JSON structures received to be forgiving of (ignore) fields you are not expecting to receive. It is quite common for Brightspace to add fields to the JSON structures sent to APi clients, even withing the same APi version used by client requests.

Paged data

Some actions can prompt the service to return very large sets of results. In these cases, the service may return data to callers in segments of the entire set. You can use these segments to partition your retrieval of the entire data set (for example, as a background synchronization process), or to create an interactive way for users to page through the data set until they find a particular item they’re looking for.

The service provides a number of ways to do paging.

Paged result sets

In paged result sets, the service wraps each of these result segments using a PagedResultSet composite structure like this:

Api.PagedResultSet
{
   "PagingInfo": {
      "Bookmark": <string>,
      "HasMoreItems": <boolean>
   },
   "Items": [
       { <composite:first_item_in_this_set> },
       { <composite:second_item_in_this_set>}, ...
       { <composite:nth_item_in_this_set> }
   ]
}

The service uses a Bookmark property to act as a paging control value:

  • Each time the service provides you with a segment of the entire data set, the Bookmark property identifies the last item in the returned segment.

  • Each time you use an action to retrieve items from the entire data set, you can use a bookmark query parameter to indicate that you’ve already received the segment containing that item, and you want the next segment from the set. If you use the action with an empty or absent bookmark parameter, then the service always returns the first segment.

The service uses a HasMoreItems property to indicate that its containing segment is not the last segment in the data set.

Paging value opacity. In some cases, the service will use one of the properties inherent to the items in the data set as a paging value. For example, in a data set of users, the service may directly use the UserId property as a paging value. Where this is the case, the documentation for the action in question will tell you. If the documention doesn’t specifically tell you that a bookmark value has some underlying inherent meaning, should not assume that it does, and you should assume that the value is just an opaque string with no particular meaning (other than to get used as a bookmark).

When the paging value does use a documented value with meaning, this could be useful to you in several ways:

  • You can use that property from any known item to act as a bookmark (for example, given any arbitrary user’s UserId property, you can always request the segment that would follow that user’s position in the entire data set).

  • You can use a bookmark’s value elsewhere where you might otherwise need that kind of value (for example, if the action uses the UserId as a paging value, you can use a bookmark’s value in another action that would use a UserId as a parameter).

Sorting. Notice that, although the service does guarantee that it will have sorted the entire data set upon some key, it does not guarantee to use the same key for sorting and paging control. In fact, in most cases, the sorting key and the paging control property are entirely different. For example, the service may sort the entire data set on “last modified time”, but provide paging control with a property like UserId. Accordingly, if the entire data set gets resorted in between two uses of an action, the segment you got from the first use of the bookmark may not naturally match up with the segment you get from the second use.

Missing bookmarks. Because the service always maintains a unique mapping between paging values and the items in the entire data set, if a “bookmarked” item gets deleted between your two uses of an action, the second attempt may not succeed because the service can no longer find the item that maps your provided bookmark parameter and may, therefore, not be able to determine the item it should use as the first item in the next segment to return to you.

In cases where the paging property is not opaque (that is, it’s a UserId or some other known item property), you can always fall back to trying the second-to-last item from the previously fetched segment. Accordingly, it may be wise to maintain a cache of some recently retrieved paging values when you know they’re not opaque values.

In cases where the paging property is opaque, you may want to cache previously returned Bookmark values, so you can at least attempt to “re-fetch” a segment and see what it’s returned bookmark value is.

Object list pages

In object list pages, the service dispenses with the need to manipulate and track bookmarks; the service wraps each of these result segments using a ObjectListPage composite structure like this:

Api.ObjectListPage
{
    "Next": <string:APIURL>|null,
    "Objects": [
        { <composite:first_item_in_this_set> },
        { <composite:second_item_in_this_set> }, ...
        { <composite:nth_item_in_this_set> }
    ]
}

Sometimes a paged list will contain objects that may, themselves, contain properties that are lists paged into an ObjectListPage:

{
    "Next": <string:APIURL>|null,
    "Objects": [
        {
           "FlatPropertyOne": <string>,
           "FlatPropertyTwo": <number>,
           "PagedListOfUsers": { <composite:Api.ObjectListPage<User.User>> }
        }, ...
    ]
}
Next

If more objects exist beyond this current page of results, then this property is not null and will contain an APIURL to fetch the next page of records.

You must still wrap the APIURL with authentication tokens to make a proper (GET) API call, but otherwise, the required route and query parameters to fetch the next page are all self-contained. Practically speaking, the service will provide back the same query parameters (if any) you used to filter/sort/search through data with your original request.

Objects

The generic paging wrapper folds an array of the underlying data objects being paged into this property.

Disposition and error handling

In order to directly interpret the results of an action, refer to the HTTP result code, the reference entry for the particular action, and these general guidelines. Note that the API reference entries for particular actions generally describe only the specific 2xx and 4xx status return codes you might encounter with successful, or unsuccessful, client requests; here, we explain the general behaviour of the Brightspace APIs you can expect (including what 3xx and 5xx status codes might mean).

200: OK

Service returns an empty response body, or a JSON data block that you can pass to a JSON parser for deserialization.

3xx: Redirection responses

Service is indicating that the resource underlying a particular API action has been moved to another location. Such a response may contain the URL of the desired resource’s new location.

If you receive a redirect response with a new redirection URL, you must check whether the domain of the new URL lies within the same domain as the back-end service hosting your original API action; you should only apply authentication to the redirection URL if it lies within the same domain as the back-end service hosting your original API action.

400: Bad Request

In your action, you provided an invalid parameter value or a JSON input data block with one or more invalid values. In some cases, you may have attempted an action in a calling context that renders it invalid.

Notice that your input data was successfully bound to the data objects the back-end service expected to receive, but the values you provided in the input data are invalid for one reason or another (see the 404 response below, as well).

403: Forbidden

This can occur in three general cases:

  • Response body contains Timestamp out of range, followed by the server time (UTC Unix timestamp). The service expects a different time provided with your action and cannot accept your request: this can occur if the client clock has skewed significantly from the service’s clock. You should re-calculate the skew and retry the call using the new time. (Note our client libraries may be able to take care of calculating and maintaining clock skew for you.)

  • Response body contains Invalid Token. Authentication signatures don’t match for some reason: this can occur if the user (for the calling user context) has changed his or her password, reset tokens, or otherwise disabled cached keys. You should discard your stored User ID-key pair, and offer the authentication login process to the user again.

  • Other cases (neither Timestamp out of range nor Invalid Token). The application or calling user context does not have the permissions required for the attempted action. This can occur if, for instance, you attempt an action to update course content within a user context that does not have the permission to edit the course’s content. You should notify the user about the lack of permission: users require the same permission to act through the Learning Framework API that they would for a similar action through the service’s standard web application.

404: Not Found

The response body may contain an explanatory message, or may not. Your action identified (either in the URL route, or with a parameter) a resource that the service cannot locate. You should notify the user about the missing resource.

Note that when you make an API call, the back-end service looks for the service handler associated with the API route that you called (for example, /d2l/api/versions/) and associated with the incoming data objects that service handler expects. This means that the back-end service deserializes and binds your provided parameterized data (query parameters and provided JSON data) as part of the same process that “looks for the resource handler” for your API call. If the back-end service cannot, for some reason, succeed at de-serializing and binding your parameters, this can cause the service to conclude that it “cannot find the resource”, and thus return a 404 error.

If you’re receiving a 404 when calling the API, carefully verify that you’re providing:

  • The correct API route (including any trailing slash).

  • Correct values for all the in-route variable values (version number, org unit identifiers, and so on).

  • Valid field data for the JSON structures you’re expected to provide: this is especially true of JSON properties that have complex, parsed values (for example, “valid email addresses”, “valid URLs”, “valid timestamps”, and so on).

Finally, note that the back-end service may not have all the Brightspace functionality enabled to provide access to all the APIs contained in this API reference documentation. If you attempt to make an API call to an action for which the underlying supporting functionality has not been enabled, your call can result in a 404 status code.

405: Method Not Allowed

The response body may contain an explanatory message. You attempted to use an HTTP method that’s not permitted with a route that does exist for use with other methods. (For example, this can occur if you attempt to DELETE a resource that can only be retrieved with GET.)

409: Conflict

The response body may contain an explanatory message. Your action required access to a resource and the access resulted in an access conflict. This can occur in the following cases:

  • You attempt to create a resource with a name that already exists.

  • You attempt to alter a resource that is not yet ready to be altered.

415: Unsupported Media Type

The response body may contain an explanatory message. If the action you’re using expects input, and your Content-Type header of your request specifies a media type that the back-end service cannot find a request handler for, then the service may return this error. This may occur, for example, if you use an action which requires application/json input, and your request does not specify this media type (or provides another, like plain/text).

429: Too many requests

If an API caller exceeds its allowed calling rate, the back-end service may respond with a 429 status code. If you encounter a result like this, you must wait until your limit has been reset before the back-end service will once again process your API calls.

500: General service error

The response body is empty. The service has encountered an unexpected state and cannot continue to handle your action request.

If you encounter a result like this, you should report it, with system logs, to D2L’s customer support desk.

504: Service Error

The respnose body may contain an explanatory message. Typically, if the service handling your request encountered a service error from another internal service while trying to fulfill your request, it may send back this error.

Composite Error

If multiple errors occur while handling your request, then the back-end service may return a Composite Error explanatory message that can include details for the various errors that occurred.

The status code given to the response varies depending upon the makeup of the errors batched together:

  • If all the errors have the same error code, then the service uses that error code for the overall response.

  • If the errors are not all the same, but all fall in the 4*xx* range, then the service uses the status code 400.

  • Otherwise, the service uses the status code 500.

Troubleshooting

Unexpected route action results. Note that the back-end service deals subtly differently with parameters passed within the route itself to the parameters you passed as quoted name-value pairs. When the service attempts to match a route to the handling code to service the action request, an inappropriately formatted parameter in the route can cause the back-end service to assign the action to an inappropriate handler (that is, the back-end service can misunderstand what action you are trying to to take).

This can produce unexpected results. For example, your calling user context may not have permission to take the action the back-end service incorrectly assumes that you want to attempt; the service may then return a 403 status (permission problem): appropriate for the action the service thinks you’re attempting, but not appropriate for the action you think you’re attempting.

If you receive unexpected results from an API call, ensure that you’re providing a well formed route for the action you want. For example, if you have to provide a D2LID parameter within the route (for example, to identify a User ID), you should make sure that portion of the route can be interpreted as a numeric value.

Permissions issues. Brightspace APIs’ actions map fairly directly to actions (or groups of actions) a user can take from within the standard LMS web-application user interface. Accordingly, when testing your application against a server, you may find it helpful to use the standard web-application UI, logged in as the same user your web-application employs, to verify that the user context has permission to perform the relevant actions. For example, if your application needs to retrieve grades, use the standard web-application UI to check your logged in user can see grades.

«  Typographical conventions for this reference   ·  [   home  ·   reference  ·   community   |   search  ·   index   ·  routing table   ·  scopes table   ]   ·  API versions (working to a common version)  »