To use the the API to Canvas you need an access token. Access tokens are specific to your user account and Canvas domain. Your Canvas domain, CANVAS_DOMAIN
, is the URL of your institution’s Canvas instance, e.g. https://oregonstate.instructure.com or, the instance provided by Instructure https://canvas.instructure.com/.
You can request an access token at: {CANVAS_DOMAIN}/profile/settings
, under “Approved Integrations:”. Once generated, your token is only visible once so make sure you copy it.
To verify your token and domain, pass them to the .token()
and .api_endpoint
arguments of cnvs_whoami()
:
cnvs_whoami(.token = "mvvGbKyGK9n5T57qhEu8K1sNMt85OLoNGTepqd3v5NEcWMuxArSz5aaXppPjodr5eU", .api_url = "https://canvas.instructure.com")
The result should be successful and include your name and login id:
"name": "Charlotte Wickham",
"login_id": "cwickham@gmail.com",
"domain": "https://canvas.instructure.com",
"token": "mv..."
It is convenient to set environment variables to store your domain and token. cnvs looks for these in CANVAS_DOMAIN
and CANVAS_API_TOKEN
respectively. The easiest way to set them is to edit your .Renviron
file:
# install.pacakges("usethis") usethis::edit_r_environ()
Add lines like these substituting in your own domain and token:
CANVAS_DOMAIN="https://canvas.instructure.com"
CANVAS_API_TOKEN="mvvGbKyGK9n5T57qhEu8K1sNMt85OLoNGTepqd3v5NEcWMuxArSz5aaXppPjodr5eU"
Make sure your .Renviron
file ends with an empty line.
Restart R and check by running cnvs_whoami()
with no arguments:
To make query to the Canvas LMS API use the cnvs()
function. The first argument is the API endpoint. cnvs()
is designed to make it as easy as possible to copy and paste from the Canvas API documentation.
As an example, imagine you want to see the disucssion topics in your course. Your first step is to find this task in the Canvas API docs — it is listed under the Discussions resource as List discussion topics. There are two endpoints listed there:
GET /api/v1/courses/:course_id/discussion_topics
GET /api/v1/groups/:group_id/discussion_topics
The first will list topics in a course and the second in a group — you want the first. Parts of the endpoint that are prefaced with a colon, :
, are parameters, e.g. :course_id
and :group_id
. You will need to provide these parameters to cnvs()
as named arguments (minus the :
).
To make the query, copy and paste the endpoint to the first argument of cvns()
, then add arugments for any parameters in the endpoint:
discussions <- cnvs("GET /api/v1/courses/:course_id/discussion_topics", course_id = 1732420)
I’m using a course_id
for one of my courses, you’ll need to use your own (see “How do I find my course id?”).
cvns()
returns a list, but prints this list as JSON. You can access components as you would elements in a list:
discussions[[1]]$title #> [1] "test"
Functions for parsing the results are beyond the scope of cnvs, but you can parse them yourself using iteration functions from purrr. For example, we could look at all the topic titles:
library(purrr) library(dplyr) #> #> Attaching package: 'dplyr' #> The following objects are masked from 'package:stats': #> #> filter, lag #> The following objects are masked from 'package:base': #> #> intersect, setdiff, setequal, union discussions %>% map_chr("title") #> [1] "test" "Introductions" #> [3] "How do I find my course id?" "Q & A"
Or squeeze the entire response into a tibble:
discussions %>% map_dfr(flatten_dfc) #> New names: #> * id -> id...1 #> * id -> id...27 #> * html_url -> html_url...30 #> * html_url -> html_url...31 #> New names: #> * id -> id...1 #> * id -> id...27 #> * html_url -> html_url...30 #> * html_url -> html_url...31 #> New names: #> * id -> id...1 #> * id -> id...27 #> * html_url -> html_url...30 #> * html_url -> html_url...31 #> New names: #> * id -> id...1 #> * id -> id...27 #> * html_url -> html_url...30 #> * html_url -> html_url...31 #> # A tibble: 4 x 36 #> id...1 title last_reply_at created_at posted_at podcast_has_stu… #> <int> <chr> <chr> <chr> <chr> <lgl> #> 1 8.72e6 test 2020-04-03T2… 2020-04-0… 2020-04-… FALSE #> 2 8.09e6 Intr… 2019-10-02T1… 2019-10-0… 2019-10-… FALSE #> 3 8.09e6 How … 2019-10-02T1… 2019-10-0… 2019-10-… FALSE #> 4 8.09e6 Q & A 2019-10-02T1… 2019-10-0… 2019-10-… FALSE #> # … with 30 more variables: discussion_type <chr>, allow_rating <lgl>, #> # only_graders_can_rate <lgl>, sort_by_rating <lgl>, #> # is_section_specific <lgl>, user_name <chr>, #> # discussion_subentry_count <int>, attach <lgl>, update <lgl>, reply <lgl>, #> # delete <lgl>, user_can_see_posts <lgl>, read_state <chr>, #> # unread_count <int>, subscribed <lgl>, published <lgl>, can_unpublish <lgl>, #> # locked <lgl>, can_lock <lgl>, comments_disabled <lgl>, id...27 <int>, #> # display_name <chr>, avatar_image_url <chr>, html_url...30 <chr>, #> # html_url...31 <chr>, url <chr>, pinned <lgl>, can_group <lgl>, #> # locked_for_user <lgl>, message <chr>
The Canvas API docs also describe parameters that can be passed in the body of a query to each endpoint in the “Parameters” section. You can add these as additional named arguments to cnvs()
. For example, for List discussion topics a possible parameter is only_announcements
, a boolean, that controls whether only annoucements are returned. You could add this to the call the cnvs()
as another argument:
announcements <- cnvs("GET /api/v1/courses/:course_id/discussion_topics", course_id = 1732420, only_announcements = TRUE) announcements %>% map_chr("title") #> [1] "cnvs: avoiding point and click hell"
Some parameters expect a more complicated object, for example the parameters to create a module, include:
module[name]
module[unlock_at]
module[position]
This notation, a parameter name followed by another in square brackets, indicates a hierachical structure. Canvas is expecting the module
parameter to be a JSON object. In cnvs()
this translates to a named list, e.g.:
new_module <- cnvs("POST /api/v1/courses/:course_id/modules", course_id = 1732420, module = list( name = "First module", unlock_at = "2019-09-01T6:59:00Z", position = 1 ))
If the square brackets are empty, Canvas is expecting a JSON array, which translates to an unnamed list in cnvs()
. An example of this structure is the quiz_extensions
parameter to set quiz extensions, e.g.
quiz_extensions[][user_id]
quiz_extensions[][extra_attempts]
One option is to visit your course in Canvas and examine the URL:
https://canvas.instructure.com/courses/1732420
Your course ID comes right after /courses/
, e.g. 1732420 in this case.
Alternatively, from R, the default cvns()
endpoint is `/api/v1/courses’ which will list all your courses:
my_courses <- cnvs()
Then you need to parse them and look for the id
columns in the appropriate row:
my_courses %>% map_dfr(compose(compact, tibble::as_tibble, .dir = "forward")) #> # A tibble: 8 x 28 #> id name account_id uuid start_at grading_standar… is_public created_at #> <int> <chr> <int> <chr> <chr> <int> <lgl> <chr> #> 1 2.16e6 "Dat… 81259 q6WF… 2020-07… 131126 FALSE 2020-07-1… #> 2 8.56e5 "Dev… 81259 n1lm… 2014-05… NA FALSE 2014-05-1… #> 3 9.46e5 "ST4… 81259 O8tA… 2015-06… 50798 TRUE 2015-06-1… #> 4 9.78e5 "ST5… 81259 4Ogf… 2015-10… 56566 TRUE 2015-10-1… #> 5 1.17e6 "ST5… 81259 24SS… 2017-06… 81573 TRUE 2017-06-1… #> 6 9.44e5 "Sta… 81259 EqGR… 2015-06… NA FALSE 2015-06-0… #> 7 9.44e5 "Sta… 81259 EqGR… 2015-06… NA FALSE 2015-06-0… #> 8 1.73e6 "Tes… 81259 RIIU… <NA> NA FALSE 2019-10-0… #> # … with 20 more variables: course_code <chr>, default_view <chr>, #> # root_account_id <int>, enrollment_term_id <int>, license <chr>, #> # public_syllabus <lgl>, public_syllabus_to_auth <lgl>, #> # storage_quota_mb <int>, is_public_to_auth_users <lgl>, #> # apply_assignment_group_weights <lgl>, locale <chr>, calendar <named list>, #> # time_zone <chr>, blueprint <lgl>, enrollments <list>, #> # hide_final_grades <lgl>, workflow_state <chr>, #> # restrict_enrollments_to_course_dates <lgl>, #> # overridden_course_visibility <chr>, end_at <chr>