Developer Platform (July 2017)

Action requests

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

. title:: Brightspace API calling conventions

Contents

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.

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. Your use of the API can generally be done in two ways:

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:

http://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 http://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 a multistage process to establish an authenticated application and user context for actions. Because of the stateless nature of REST-like APIs, after your client application establishes this context you must provide parameters that signal the context to the service with every action call that you make:

x_a
Application ID for the calling client application, as assigned by the Keytool service. This is a static value determined out-of-band, not dynamically: you should compile it into your client application, or place it in a configuration value location your client application can read at run-time.
x_b
User ID for the current calling user context as gathered from the authentication process. During the authentication process, the service provides you with this value.
x_c
Application signature result. This should be a one-way hash of a base string comprising path, timestamp, and HTTP method, together with the client application’s key.
x_d
User signature result. This should be a one-way hash of a base string comprising path, timestamp, and HTTP method, together with the calling user’s key.
x_t
Current UTC time as a Unix timestamp value (decimal integer value). The caller should set this to the caller’s time, but adjust it for the server’s skew (if known) to accommodate the server time.

The authentication system requires callers to create the signature result tokens before encoding them as parameters in URLs: see the authentication topic for more details on how to do that.

Example. Here’s what a fully specified URL might look like for a sample action (note that this would be used as one entire string with no line breaks):

https://lms.valence.desire2learn.com/d2l/api/lp/1.0/users/whoami?x_a=uNpXZUmQS_eRn6-cRluTGQ&x_b=XkVfdn0Cq0QEv8hRGWnrZr&x_c=os5iA4GrRk8UJki_CsJxzOpN2P_Ya6lTHxQ_43Kxk94&x_d=fnxGRHKWT0MGeK8qCah06y9BmD_3oE5AKwlEZ0k32hU&x_t=1317786116

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

http://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:

http://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”.

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.

Note

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.

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

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.

Disposition and error handling

Our client library packages typically provide you with tools to interpret the disposition of your API actions; we recommend that you use these libraries rather than directly attempting to interpret HTTP result codes.

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 reference entries for particular actions generally cover the specific return codes you might encounter with those actions; here, we explain the general behaviour of the Brightspace APIs you can expect.

200: OK
Service returns an empty response body, or a JSON data block that you can pass to a JSON parser for deserialization.
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).
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 cases where your action seeks to create a resource by name, where a resource with that name already exists.
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 exmaple, 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).
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.

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

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 wan 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 it’s 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.

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.

Logging. If you have access to the back-end service’s web server logs, you can identify all the calls to the API by filtering the logs on <hostname>/d2l/api, as all the API calls occur under this path.

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