I want to transact data with the Queensland University of Technology (QUT) Canvas Learning Management System (LMS) via the Canvas LMS representational state transfer (REST) web application programming interface (API).
But more, much more than this, I want my colleagues to be able to enjoy programmatic access to Canvas so that we can spend more time teaching and less time pointing and clicking at a Learning Management System to do repetitive tasks inefficiently.
Yesterday, I was kicking ideas around with Dr Jake Bradford about how best to write code to interface with Canvas via its API.
Postman can convert an API request into a code snippet, and you can choose the programming language or framework. You can use this generated code snippet in your front-end applications.
Today I am going to give it a go, and with some urgency as we have a bunch of DSB100 marks and feedback to get back to students ASAP.
This course will introduce you to Postman and is suited for beginners. You will learn how to build API requests with Postman, how to inspect responses and create workflows. Postman Beginner’s Course - API Testing
I sign up for a free account, log in,
GET https://canvas.qut.edu.au/doc/api/api-docs.json
CORS Error: The request has been blocked because of the CORS policyIf the Postman web app fails to send your request, you may be experiencing a cross-origin resource sharing (CORS) error. Make sure you’re using the best Postman Agent for your request.
I download and install the Desktop Postman Agent, then switch to that agent in the Postman window in Chrome and…
GET https://canvas.qut.edu.au/doc/api/api-docs.json
returns a boatload of jsonVisualise response
API Path and
DescriptionThis is looking very good.
The Beginners Course looks good, but I’m going to see what I can figure out on my own, including how to…
QUT Canvas, then
create
canvas =
https://canvas.qut.edu.autoken =
<My secret API key>QUT EnvironmentGET request
{{canvas}}/doc/api/api-docs.jsonjsonrcanvas::get_course_list()GET {{canvas}}/api/v1/courses
"user authorisation required"Type to Bearer Token and
Token to {{token}}, the environment variable I
set to <My secret API key>I have set the following environment variables
| key | value |
|---|---|
domain |
https://canvas.qut.edu.au |
token |
<My secret API key> |
api |
api/v1 |
canvas |
{{domain}}/{{api}} |
GET {{canvas}}/courses
</> icon at the rightR-httr as the code language and get the
following code
library(httr)
headers = c(
'Authorization' = 'Bearer <My secret API key>'
)
res <- VERB("GET", url = "https://canvas.qut.edu.au/api/v1/courses", add_headers(headers))
cat(content(res, 'text'))The code (appears to) runs perfectly and return the desired
json.
get_courses()get_courses <- function(){
headers = c(Authorization())
res <- VERB(
"GET",
url = "https://canvas.qut.edu.au/api/v1/courses",
add_headers(headers)
)
cat(content(res, 'text'))
}Here’s the result of get_courses piped through
jsonlite::prettify…
[{"id":4089,"name":"DSB100_21se2 Fundamentals of Data Science","account_id":146,"uuid":"E3dkRXXiASbNn6xANbiwNAQ...
...enrollments_to_course_dates":false}]Error: parse error: premature EOF
(right here) ------^
Hmmm… maybe this requires some pagination:
> Requests that return multiple items will be paginated to 10 items
by default. You can set a custom per-page amount with the
?per_page parameter.
per_page parameter to the query in Postman and get:get_courses <- function(){
headers = c(Authorization())
res <- VERB(
"GET", url = "https://canvas.qut.edu.au/api/v1/courses?per_page=200",
add_headers(headers)
)
cat(content(res, 'text'))
}…but that still chokes on an end of file.
This looks like the kind of thing httr2 is meant to
assist with.
httr2 bulletLet’s try following this httr2 vignette on Wrapping
APIs
# Build the request
req <- request("https://canvas.qut.edu.au/api/v1") |>
req_url_path_append("courses") |>
req_auth_bearer_token(my_CanvasAPI_key())
# Give it a dry run
req |> req_dry_run()GET /api/v1/courses HTTP/1.1
Host: canvas.qut.edu.au
User-Agent: httr2/0.2.3 r-curl/5.1.0 libcurl/8.3.0
Accept: */*
Accept-Encoding: deflate, gzip
Authorization: <REDACTED>
The body of the response is lengthy:
[1] 16799
…so here’s a taste of that content
[
{
"id": [4089],
"name": ["DSB100_21se2 Fundamentals of Data Science"],
"account_id"
…and here’s the response status:
[1] 200
[1] "OK"
tibble(
course_id = map_int(json, "id"),
course_code = map_chr(json, "course_code"),
name = map_chr(json, "name")
)# A tibble: 10 × 3
course_id course_code name
<int> <chr> <chr>
1 4089 DSB100_21se2 DSB100_21se2 Fundamentals of Data Science
2 12878 DSB100_22se2 DSB100_22se2 Fundamentals of Data Science
3 14567 DSB100_23se2 DSB100_23se2 Fundamentals of Data Science
4 2792 EGH400-1_23se1 EGH400-1_23se1 Research Project 1
5 14584 EGH400-1_23se2 EGH400-1_23se2 Research Project 1
6 2796 EGH400-2_23se1 EGH400-2_23se1 Research Project 2
7 14585 EGH400-2_23se2 EGH400-2_23se2 Research Project 2
8 14587 EGH408_23se2 EGH408_23se2 Research Project
9 14824 IFN657_23se2 IFN657_23se2 Principles of Software Security
10 2927 IFN703_23se1 IFN703_23se1 Advanced Project
Also, we can check to see whether there are additional pages of items
to be returned. (See Canvas
API Pagination, Parse link
URL from a response.) In this case, the response from Canvas
(resp) contains a link to the url to get the next set of
items:
[1] "https://canvas.qut.edu.au/api/v1/courses?page=2&per_page=10"
If we request a larger number of items per page…
resp.100 <-
request("https://canvas.qut.edu.au/api/v1") |>
req_url_path_append("courses") |>
req_auth_bearer_token(my_CanvasAPI_key()) |>
req_url_query(per_page = 100) |>
req_perform()…all available items can be delivered in the one response, and there is no “next” url:
NULL
httr2 to compile paginated responsesThe developer of httr2
has thoughtfully provided ways to compile paginated responses… I just
have to figure them out. I am going to try to