Celebrate Excellence in Education: Nominate Outstanding Educators by April 15!
Found this content helpful? Log in or sign up to leave a like!
Have a question about the Canvas APIs? Have a cool API integration you'd be willing to share? If so, please post here.
@James was correct, instead of the todo_date that is returned when you query the page information, you have to submit the update as student_todo_at.
Hi there!
I am fighting with the api trying to create "calculated_question" quizzes avoiding the Canvas web interface. I.e. my goal is to have a script populate/modify the table of variable values and results.
First I used the Python canvasapi library and was able to create a calculated_question and populate the table of pre-calculated values, but the answer-field is always reset to 0.0 and also the field for the formula is not populated. I then tried to modify an existing calculated_question using canvasapi and was not at all able to get any result.
I expected this to be a problem in the canvasapi module, so I used postman to send the data directly to the api. Creating a new calculated_question here is even more frustrating since only a "text_only_question" is created from the json object. Trying to modify the question afterwards with a second POST-call only results in an "422 Unprocessable Entity" error.
Some MWEs:
a) working example creating a new calculated_question using canvasapi in python. However, neither the 'answer' values nor the 'formula' field is accepted
new_question = {
'question_name': "Q4",
'question_type': 'calculated_question',
'question_text': '<p>[a]*[b]=?</p>',
'points_possible': 1.0,
'correct_comments': '',
'incorrect_comments': '',
'neutral_comments': '',
'correct_comments_html': '',
'incorrect_comments_html': '',
'neutral_comments_html': '',
'answers': [{'weight': 100,
'variables':
[{'name': 'a', 'value': '8'},
{'name': 'b', 'value': '4'}],
'answer': 32.0},
{'weight': 100,
'variables':
[{'name': 'a', 'value': '4'},
{'name': 'b', 'value': '4'}],
'answer': 32.0}],
'variables': [{'name': 'a', 'min': 1.0, 'max': 10.0, 'scale': 0},
{'name': 'b', 'min': 1.0, 'max': 10.0, 'scale': 0}],
'formulas': [{'formula': "a*b"}],
'answer_tolerance': '5%',
'formula_decimal_places': 1,
'matches': None,
'matching_answer_incorrect_matches': None,
}
question = quiz.create_question(question=new_question)
-> resulting json structure
{
"id": 63460352,
"quiz_id": 3793795,
"quiz_group_id": null,
"assessment_question_id": 100291102,
"position": null,
"question_name": "Q4",
"question_type": "calculated_question",
"question_text": "<p>[a]*[b]=?</p>",
"points_possible": 1,
"correct_comments": "",
"incorrect_comments": "",
"neutral_comments": "",
"correct_comments_html": "",
"incorrect_comments_html": "",
"neutral_comments_html": "",
"answers": [
{
"weight": 100,
"variables": [
{
"name": "a",
"value": "8"
},
{
"name": "b",
"value": "4"
}
],
"answer": 0,
"id": 6824
},
{
"weight": 100,
"variables": [
{
"name": "a",
"value": "4"
},
{
"name": "b",
"value": "4"
}
],
"answer": 0,
"id": 7147
}
],
"variables": [
{
"name": "a",
"min": 1,
"max": 10,
"scale": 0
},
{
"name": "b",
"min": 1,
"max": 10,
"scale": 0
}
],
"formulas": [
{
"formula": ""
}
],
"answer_tolerance": "5%",
"formula_decimal_places": 1,
"matches": null,
"matching_answer_incorrect_matches": null
}
b) POSTMAN json structure which will only result in a new "text_only_question" when send to the /api/v1/courses/:course_id/quizzes/:quiz_id/questions endpoint
{"quiz_group_id":null,"position":null,"question_name":"Q6","question_type":"calculated_question","question_text":"\u003cp\u003e[a]*[b]=?\u003c/p \u003e","points_possible":1.0,"correct_comments":"","incorrect_comments":"","neutral_comments":"","correct_comments_html":"","incorrect_comments_html":"","neutral_comments_html":"","answers":[{"weight":100,"variables":[{"name":"a","value":"8"},{"name":"b","value":"4"}],"answer":64},{"weight":100,"variables":[{"name":"a","value":"4"},{"name":"b","value":"4"}],"answer":42}],"variables":[{"name":"a","min":1.0,"max":10.0,"scale":0},{"name":"b","min":1.0,"max":10.0,"scale":0}],"formulas":[{"formula":"a+b"}],"answer_tolerance":"5%","formula_decimal_places":1,"matches":null,"matching_answer_incorrect_matches":null}
if the same structure is sent to the endpoint of an already existing question, a 422-error is created.
Am I missing something or is it simply impossible to create a calculated_question through the api - if so, why? I could not find any comment or any example anywhere...
answering my own problem at least partially: I now managed to get the formula to get populated following some breadcrumbs I found here: https://community.canvaslms.com/message/142236-re-uploading-answers-to-formula-question-using-api?co...
new_question = {
...
'formulas': ['a*b'],
...
}
question = quiz.create_question(question=new_question)
Trying to do something similar with the answer-field in
'answers': [{'weight': 100,
'variables':
[{'name': 'a', 'value': '8'},
{'name': 'b', 'value': '4'}],
'answer': 32.0},
{'weight': 100,
'variables':
[{'name': 'a', 'value': '4'},
{'name': 'b', 'value': '4'}],
'answer': 32.0}],
doesn't work though....
...and that was because the out-going field name for the answer value is "answer_text" and not "answer".
I finally solved my problem - now where can I contribute to document this for future users who will have the same problem?
new_question = {
'question_name': "Q6",
'question_type': 'calculated_question',
'question_text': '<p>[a]*[b]=?</p>',
'points_possible': '1.0',
'correct_comments': '',
'incorrect_comments': '',
'neutral_comments': '',
'correct_comments_html': '',
'incorrect_comments_html': '',
'neutral_comments_html': '',
'answers': [{'weight': '100',
'variables':
[{'name': 'a', 'value': '8'},
{'name': 'b', 'value': '4'}],
'answer_text': '32.0'},
{'weight': '100',
'variables':
[{'name': 'a', 'value': '4'},
{'name': 'b', 'value': '4'}],
'answer_text': '42.0'}],
'variables': [{'name': 'a', 'min': '1.0', 'max': '10.0', 'scale': 0},
{'name': 'b', 'min': '1.0', 'max': '10.0', 'scale': 0}],
'formulas': ['a*b'],
'answer_tolerance': '5%',
'formula_decimal_places': '1',
'matches': None,
'matching_answer_incorrect_matches': None,
}
question = quiz.create_question(question=new_question)
You could create a blog post to document your findings. The Canvas Developers group, which is where this message is housed, is the best place for it. Go to the home page for the group and click on "Actions" at the top right and choose "Blog Post".
If don't think you have enough for a blog post, you could comment on the other thread: Uploading answers to formula question using API . That's the one I was going to recommend when I read your first message -- until I got to your second and third post, that is.
Alternatively, you may consider what you wrote here to be documentation. The benefit of having it somewhere else is that people can search by title and not have to wade through 10 pages of post to find it. That is one of the concerns when you have a broad title like "all things API." On one hand, you get lots of people following it so there are lots of people who may respond, but on the other hand, it's easy to lose things because it's so long.
I am right now extending this little piece of code to a "useful" example from the field of electronics which I will gladly put into a blog-post later today or during the weekend!
So I'm trying to connect to a basic endpoint: "https://my.instructure.com/api/v1/courses".
I'm trying to do a simple curl request and execution of it:
$url = "https://my.instructure.com/api/v1/courses"
$headers = ['Authorization Bearer ' . $myToken];
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => TRUE,
CURLINFO_HEADER_OUT => TRUE,
CURLOPT_URL => $url,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_SSL_VERIFYPEER => TRUE,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HEADER => TRUE
]);
$resp = curl_exec($curl);
$header_size = curl_getinfo( $curl, CURLINFO_HEADER_SIZE );
$header = substr( $resp, 0, $header_size );
$body = substr( $resp, $header_size );
When I do curl_getinfo($curl, CURLINFO_HTTP_CODE), I get 401, which is the unauthorization error as we see here: 401 Unauthorized Error: What It Is and How to Fix It
But I'm not sure how it can be happening. I have admin permissions over my instructure. I am using a correct and recently generated token. The code I have given works perfectly well for other cURL endpoint requests. What more is missing? What could I be doing wrong? Any advice would be greatly appreciated
<?php
$url = "https://xxx.instructure.com/api/v1/courses/";
$myToken = "xxx";
$headers = ['Authorization: Bearer ' . $myToken];
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => TRUE,
CURLINFO_HEADER_OUT => TRUE,
CURLOPT_URL => $url,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_SSL_VERIFYPEER => TRUE,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HEADER => TRUE
]);
$resp = curl_exec($curl);
//print($resp);
$header_size = curl_getinfo( $curl, CURLINFO_HEADER_SIZE );
//print($header_size);
$header = substr( $resp, 0, $header_size );
//print(strlen($header));
$body = substr( $resp, strlen($header));
print($body)
?>
Thank you for that. However I still have the same problem: print($body) gives the same unauthorised error message. I did a dd($curl) after the curl_setopt_array and got this result.
Does this tell you anything?
Hi Lee,
I can confirm that that Gerald’s code works as posted.
If you’re seeing an authorization error then I’d suggest looking in two places: first the key. I know you’ve said you use this token elsewhere, but double-check that you cut and pasted the entire code, and only the code, double quoted, as $myToken. IME 9 times out of ten when I get errors like this I’ve fat-fingered something…
Also make sure that the headers array appears exactly as Gerald has it. The spaces before and after ‘ Bearer ‘ are significant, as well as the colon.
If it’s not a formatting issue or typo, from your dd() reference I assume you’re using Laravel? Is it possible that curl has been subclassed somewhere in a way that’s interfering with your request? Have you tried a different mechanism, like guzzle?
The first code snippet that you had was missing the : between Authorization and Bearer. The second post that you have uses account ID of 1. I would try using self instead of 1. If you are using a hosted account, which it looks like since you have instructure.com in the url, there is a really good chance (approaching 100%) that you do not own account 1. That would give you unauthorized error. It may be something else, but those are two things that jump out at me in the code you supplied.
Lee,
I was indeed missing the : which was my mistake. That is corrected now. Thank you!
That, combined with a (fifth) token change per @jsavage1 's suggestion, seems to have done the trick for now. Thank you both!
I want to authenticate access to a canvas webpage based not on an API token key, but on an existing session, so that people have to log into accounts that have the matching permissions in order to access the page. How can I do this in Canvas?
EDIT: I am using Laravel, if it's of any interest, and I am making my own custom webpages for it.
@sam_ofloinn , Canvas lacks fine grade access control at the level of individual pages - to a first approximation, all of the content in a course has the same level of access as any other. However, finer-grained access control can be done for an LTI application that has been installed. You can see examples of doing this authentication in Ruby at https://github.com/gqmaguirejr/E-learning (see the SinatraTestxx.rb, chipit.rb, and adminit.rb programs). The LTI application runs in a frame within the Canvas window. One could use an LTI app to provide very fine-grained access control or even a capabilities based access control mechanism, but you would have to implement all of the mechanism yourself.
Note that one could store a given user's capabilities using the user's custom_data (see https://canvas.instructure.com/doc/api/users.html#method.custom_data.set_data). I have used this type of data to store information about a student's program of study (in the above ruby program). Now before the LTI app presents data to the user it can check the custom_data for a capability to access this data.
Thank you. Do you have any advice for someone using Laravel?
I do not have any experience with Laravel. However, as it is a PHP environment - you should be able to make your public/index.php file do the Oauth verification - presumably using Laravel's Passport.
EDIT: For anyone reading this and using Laravel/Canvas, I would not recommend using Passport for OAuth2 authentication/authorisation. Passport creates an OAuth2 server in your project for you to use; Canvas already has one created for you. You can create clients, developer keys and such on Canvas. See here:
Developer Keys - Canvas LMS REST API Documentation
OAuth2 - Canvas LMS REST API Documentation
I'm getting data on user accounts (as can be seen here Users - Canvas LMS REST API Documentation ). You can see an example of what this looks like for me in the picture below.
Is it possible to add a field value to this array for users? If so, how, and is it recommended?
@sam_ofloinn , what kind of field to you want to add? I frequently use joins/cats/... and other operations by using python Pandas - so I can put together user information with other information - such as how has been assigned as a peer reviewer for whom. See for example: Canvas-tools/list-peer_reviewing_assignments.py at master · gqmaguirejr/Canvas-tools · GitHub and Canvas-tools/students-in-my-courses-with-join.py at master · gqmaguirejr/Canvas-tools · GitHub
Gerald,
I was thinking something as simple as a permission field. e.g for one user it might read "permissions => default", while an admin would read "permissions => admin". How would that go about?
For clarity's sake, this is in PHP, via the Laravel framework
@sam_ofloinn , You can easily pass via the LTI interface information from Canvas about what roles a user has.
However, building a full permissions based access scheme where different people with the same role have different permissions requires storing this information somewhere - thus is were the custom user information could be used.
Interesting, thank you. It certainly seems like an LTI application is the way to go. I'm new to the concept of LTIs, and I've been reading on them here: Edu App Center - Basics - Introduction . But I'm interested in getting started on them ASAP.
If using the PHP Laravel framework, do you have any suggestions on how to get started making an LTI app for Canvas?
Check out the code at GitHub - ucfopen/lti-template-php
Gerald, thank you for that that library, it and the other ones seem very convenient. I'm able to get other libraries like "XML to Array" and "XML Collection" installed.
However, despite having Composer, I'm not able to run some of the key commands?
"php composer.phar install"
doesn't seem to work, unfortunately, and just says "could not open input file: composer.phar"
In addition, I'm testing on my local machine at the moment, meaning I don't have any Canvas functionality (or so I'm guessing, still new to Canvas still). How might you suggest I test that some LTI applications, like the extensions from Edu App Center, are working?
I built a version of Canvas according to the github Quick Start instructions: Quick Start · instructure/canvas-lms Wiki · GitHub
This enables me to have Canvas (at home and in my office) running as a set of containers. I have built it in a VM, so that I can easily give a copy of a system that is ready to run and configured suitable for development - to students who are working with me on Canvas related developments. I have built the VM using VirtualBox. There are several advantages to such a system: (1) if one screws up - you can easily return to a checkpoint; (2) you have all the privileges that you could want; (3) you can see everything - even the details in the database; and (4) since the source code is used to build the system you know just want is running and can explore what each part of the code does. However, there are some disadvantages: (1) it is not necessarily easly to get everything working (due to lots of dependencies); (2) starting up a new system takes a lot of time [but you can snapshot this running system and thus continuing to work with it is not so bad]; (3) sometimes - you would rather not know all of the details; and (4) as it is not the supported production system there may be differences between what you see in this local system and what you experience in the supported cloud version of the service.
I have never worried much about the XML files to set up the LTI tool, as I wrote python code to deal with most of this via the Canvas API. Note, that I am not supporting production LTI tools, but rather focusing on proof of concept implementations. [Thus I have left these sorts of details to the professional staff - as they will have to support the tools that the university decides to support. All I can do (presently) is to show the feasibility of doing something. If it gets adopted, then the professional staff take it over and make a production tool.]
I have a variety of LTI applications written in Ruby that you can find at GitHub - gqmaguirejr/E-learning: E-learning project for using Canvas
Interesting, thank you. Looking at your code, I'd just like to confirm: it seems like your apps are made via requesting canvas page data, and making your own page based around that. Is that a fair summary of it? Sorry if that seems like a silly question, I'm under the weather and not very savvy with Ruby
When you call an external tool via the LTI interface you can invoke your own program (which acts as an LTI Tool Provider). The output from this program will generally appear in an iframe within the Canvas interface. Your output will not have access to the webpage outside of the iframe (as is consistent with modern web security). The Canvas LMS acts as a Tool Consumer (i.e., gets data from the tool).
In many uses of LTI the tool is used to provide an external quiz engine, content, etc. You can decide how much information to pass into this external tool. For example, this means that the external tool need not know the user's actual ID, name, etc. The external tool can check that it is called from Canvas. The external tool can return a grade for an assignment, where the external tool implements the assignment. An earlier Master's project used this functionality to provide the student with a preconfigured virtual lab environment in which the student does there lab exercise and when the student has completed the exercise returns the grade to Canvas. See the Master's thesis: On-demand virtual laboratory environments for Internetworking e-learning : A first step using docker... .
However, in my programs, the external program has an access token that it uses to get essentially any data that it wants out of the Canvas LMS. I make use of this to access user information, store and retrieve data from custom columns in the grade book, ... . For example, one of the programs gets documents that have been submitted for an assignment - extracts the title, English and Swedish abstracts, English and Swedish keywords, and other data and uses this together with information about the student to create an announcement for the student's oral presentation of their thesis. This means that the examiner scheduling the oral presentation does not need to manually extract this information from the document, but rather can have a program do this work. The examiner sets the time, date, and place of the examiner and assigns an opponent (i.e., another student who will do a peer review - both written and orally) and the program composes an announcement for the presentation. This announcement is posted as an announcement in the Canvas course and also can be placed into the university's calendar (this last functionality is not available yet - as the people responsible for the API to the calendar have not yet introduced the functionality for 1st and 2nd cycle presentation, but does support 3rd cycle presentations, i.e., doctoral-level defenses). Similarly, after the student's final thesis is submitted and approved by the examiner, the goal is to automatically enter the metadata about this thesis and enter it into the digital repository and upload the thesis itself into this repository. [The external reference (URL) to the thesis above - shows what a thesis looks like in this digital repository.]
So, if I understand you correctly, yes? That's essentially how I've done it: make cURL requests from endpoints using headers and tokens, decode the data and then perform operations based on that.
There are really two orthogonal parts to this:
1. using the LTI interface to invoke from Canvas an external LTI Tool and
2. using the Canvas API to access the Canvas endpoints.
These two things can be combined as I have done with my programs. Additionally, there are two other methods of passing data from Canvas to the LTI tool:
a. You can pass values into the tool when it is invoked and
b. Using the IMS Global Learning Consortium Learning Information Services (LIS) specification
(Note that the LTI Basic Outcomes Service is a LIS service that can be used to pass grading information from the tool back to Canvas.)
Not surprisingly one of the pieces of information that is passed from Canvas to the tool is how to invoke the LIS services.
You can find some more background about this in the Master's thesis that I mentioned in my previous posting.
Using the Canvas API you can easily make programs that can get/put information from/to Canvas. These programs can be run on any machine that can communicate with the Canvas instance.
This is in contrast to the LTI interface, where you have to install the LTI tool as an external App in the Canvas instance. Within my university you need to get each LTI tool approved before you can get it installed. These approvals are per course or per school within the university - with a very small set of tools approved for use by some category of users (typically teachers) throughout the university (for example, we have a beta release of a locally created service to post grades from Canvas to the national grade repository). Also the LTI tools run on some machine other than the machine where the Canvas instance is and other than the machine were the user is who invokes the LTI tool. [However, for development purposes I do have both the Canvas LMS instance running in containers on the same machine where I have the LTI tool running - but this is unlikely to scale well.]
So I want to use Laravel Passport authentication on my Canvas test server. I'm reading right now through the documentation here:
https://laravel.com/docs/5.8/passport#managing-clients
I want to apply this part to Canvas so I can parse the JSON it gets here. Looking at the "JSON API" header, shortly under the linked section, is where I find my problem:
"However, you will need to pair Passport's JSON API with your own frontend to provide a dashboard for your users to manage their clients."
It then gives an example for getting users and data in the Axios API. But I want to be able to get Canvas-related data. For example, "GET /oauth/clients" is cited as a way to return all the clients for the authenticated users. How can I do exactly that in Canvas?
Have you looked at https://libraries.io/github/LboroScienceIT/laravel-lti-tool and https://github.com/RobertBoes/laravel-lti? https://learntech.imsu.ox.ac.uk/blog/the-basics-of-writing-a-basic-lti-tool-provider/ gives some basics of writing an LTI tool provider and gives a PHP example. The page https://www.edu-apps.org/code.html also provides some helpful information on the POST parameters that are passed from the consumer (in this case Canvas) to your tool provider (Lavarel).
Gerald, thanks again.
The first link, I've followed the steps, but I can't find the "lti2_consumer" table in my Homestead database. (I have to use Homestead for the time being).
The second, I've git-pulled now into my working project. I'm just not sure how to apply it to my code.
The third link, I appreciate the example, though I don't want to write my own LTI from scratch.
The fourth, it’s informative like the Passport documentation. Though sadly I don’t think my project will allow me the timeframe to build my own LTI, unless it’s absolutely necessary.
Maybe it’d help if I were more specific. In my past projects, I get information from Canvas via making cURL requests to Canvas endpoints. e.g.
$courseURL = "https://my.test.instructure.com/api/v1/courses";
$myToken = $this->token; //"13518~bCaXKavhv1N2tZ7d2eZ6bQ1k2TisiYyVHEBN2XiEuL1mBOJplS4fi7kOrhPiaIbG"; //$this->token;
$headers = ["Authorization: Bearer " . $myToken];
$curl = curl_init();curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => TRUE,
CURLINFO_HEADER_OUT => TRUE,
CURLOPT_URL => $courseURL,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_SSL_VERIFYPEER => TRUE,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HEADER => TRUE
]);
As best as I understand it, this is how I can code a program that can interact with Canvas and retrieve data. But none of the documentation I’ve seen on Canvas suggests anything similar, or anything possible. I’m not sure how to make head to toe of even its LTI documentation as a result; how exactly is code supposed to look like for it?
I've been reviewing the Canvas documentation of OAuth2 endpoints, as in this link.
From here, it shows this example of what a redirect example looks like:
I'm having trouble developing my own OAuth2 redirect right now: to help pinpoint the problem, I want to confirm if I understand this part correctly. In my example, could it look like this?
https://my.test.instructure.com/login/oauth2/auth?client_id=1&response_type=code&state=*&redirect_ur...
Or even like this?
https://my.test.instructure.com/login/oauth2/auth?client_id=7&response_type=code&state=&redirect_uri...
Any clarity would be greatly appreciated.
To participate in the Instructure Community, you need to sign up or log in:
Sign In