The Instructure Community will enter a read-only state on November 22, 2025 as we prepare to migrate to our new Community platform in early December.
Read our blog post for more info about this change.
Found this content helpful? Log in or sign up to leave a like!
If these question has already been asked, please point me to the correct tree to bark up. First time posting here so, yoroshiku.
I am learning how to work with Canvas' API for work. The goal is to send off scores from a VR medical simulation into a Canvas course assignment.
I got the "Free-for-teacher" version in order to test things out.
I got an API key and set up a fake course, however when I make the Web Request, it always returns the html for the login page.
Is there another way of authorization, or is the canvas.instructure.com a big bucket for all users and there is no admin access? Would I need to obtain a canvas account from my school? I'd like a way to test out how to use the API and GET and PUT stuff as well as try out GraphQL.
I also need an answer for this!
I gave it the good ole college try and the answer is yes!
I had some issues with a difference between the script and public variables in the Unity editor.
With an access token generated from a teacher account, you can change scores write comments, get student/course/assignment/submission info and whatnot.
However, you need to be an admin or get an admin to give you a developer key, which is what I want. You can scope those keys down to specific assignments and other things. So that way if the key gets taken, it doesn't give access to all scores in the course.
As a "Free for Teacher" user, I've used the API to great effect submitting grades into Canvas. For some context, about 1000 students take our core Comp Sci class every year, and I automated the transfer of grades from our Autograder system into Canvas for all of them.
To help other "Free for Teachers" who find this thread, go to Account > Settings > Approved Integrations, and "+ New Access Token"
I've spent a while learning how to use the API with a free account (not school-affiliated). Here's the template script I wrote that I always start with.
API_KEY = 'the value that you generated on the website'
API_URL = 'https://canvas.instructure.com/'
COURSE_NAME = "Canvas Course Name Here"
ASSN = 'the name of the assignment'
def get_course(name):
canvas = Canvas(API_URL, API_KEY)
avail_courases = canvas.get_courses()
for c in avail_courases:
if c.name == name:
print(c)
return c
return None
def get_quiz(course, assn):
quizzes = course.get_quizzes()
for quiz in quizzes:
if(quiz.title == assn):
# if(quiz.assignment_id == int(ASSN_ID)):
print(quiz)
# The quiz variable is now the assignment
return quiz
return None
if __name__ == '__main__':
# Get the canvas course
course = get_course(COURSE_NAME)
course_id = str(course.id)
if(course is None):
raise ValueError("Could not find the specified course. Check the name is correct.")
# From that course, get the desired assignment
quiz = get_quiz(course, ASSN)
if(quiz is None):
raise ValueError("Could not find the specified assignment. Check the name is correct.")
For COURSE_NAME, it's the top name on the course card on your Dashboard. In the case below "CS110 - Fall 2025", NOT "CS110 Fall 2025" (not the lack of a hyphen in the latter).
There's an important distinction between a "quiz" object and an "assignment" object in Canvas. In general, once you have the Canvas Course object, you can use it to drill down to whichever assignment/quiz you want.
There's a lot of extra steps to go from the Course object > assignment object > posting grades, but the way you set up your workflow will change based on how you structure your assignments / grades, etc. At some point, when you do want to post grades, here are the functions I wrote that call the low-level Canvas REST API, because the Python canvasapi library didn't work the way I wanted.
def post_comment_on_submission(comment, assignment_id, user_id, course_id):
"""
Posts a comment on a student's submission in canvas
Args:
comment (str): The textual comment you want to add. My script auto-formats this for me
assignment_id (str): The ID of the assignment, which you can find in the URL, too or pulled from the API.
user_id (str): the ID of the student. Also found in the URL when you look in SpeedGrader or pulled from the API.
course_id (str): the ID of the course. Also found in the URL or pulled from the API
Returns True on success, False otherwise
"""
# This works, so I'm not updating this to use the API. Just going to use the old code and the hand-jammed way of calling the Live API
params = (('access_token', API_KEY), ('comment[text_comment]', comment))
# There should be zero need for this overall comment on the assignment anymore. We'll see how PA1 in Fall 2023 goes
url = "https://canvas.instructure.com/api/v1/courses/{}/assignments/{}/submissions/{}".format(course_id, assignment_id, user_id)
# print(url,params)
result = requests.put(url,params=params)
if(result.status_code == 200):
return True
return False
def delete_comment_from_submission(assignment_id, user_id, comment_id, course_id):
"""
Deletes a comment from a submission. You have to get the comment_id from the API, which means you need to
get comments, iterate through the paginated list, find the one you want, and grab its ID.
I once posted 600 comments to 600 students, only to realize an off-by-one error put the wrong comment
on every student. So I wrote this function.
Args:
assignment_id (str): The ID of the assignment, which you can find in the URL, too or pulled from the API.
user_id (str): the ID of the student. Also found in the URL when you look in SpeedGrader or pulled from the API.
comment_id (str): the ID of the comment to delete
course_id (str): the ID of the course. Also found in the URL or pulled from the API
Returns True on success, False otherwise
"""
# There should be zero need for this overall comment on the assignment anymore. We'll see how PA1 in Fall 2023 goes
url = "https://canvas.instructure.com/api/v1/courses/{}/assignments/{}/submissions/{}/comments/{}".format(course_id, assignment_id, user_id, comment_id)
# print(url,params)
result = requests.delete(url)
if(result.status_code == 200):
return True
return False
def post_score_and_comment_on_questions(user_id, comment, scores, questions, sub, question_values, assignment_id, course_id, include_partial=False):
"""
Posts scores and comments to a submission.
Args:
user_id (str): A specific student's Canvas ID to find the appropriate submission
comment (str): A single string that will be added as a comment for the student to see on their submission
scores (list): A list that contains the scores pulled from autograder for the student's submissions. These should be in the order of the question numbers
questions (paginated list): The canvasapi object that contains the questions the student answered. Used to associate scores with question IDs
sub (paginated list): The submission object that contains the questions / scores / comments
question_values (list): The point values for each question
assignment_id (str): The id of the assignment to grade/comment
Returns True if the program succeeded in posting the scores and comments. False otherwise
Bug:
While a student is taking an exam, Canvas already creates a submission object. Therefore
if you run this code while someone is taking a test, you will find a submission with no data
that will break this function
"""
update_obj = create_questions_update_object(scores, questions, question_values, include_partial)
# This will fail if someone is taking the test; even though there is no submission, their attempt is assigned a submission number which lets me get to this point
# Despite the name of the function, we only use it to post scores to the individual questions. There are no comments in the update object
try:
update = sub.update_score_and_comments(quiz_submissions=update_obj)
except:
return False
# This posts the comment on the entire submission, not just each question.
# It's easier for instructors to skim
post_comment_on_submission(comment, assignment_id, user_id, course_id)
return True
def post_score_on_questions(scores, questions, sub, question_values, include_partial=False):
"""
Updates individual question grades on a submission WITHOUT changing the comment
Args:
scores (list): A list that contains the scores pulled from autograder for the student's submissions. These should be in the order of the question numbers
questions (paginated list): The canvasapi object that contains the questions the student answered. Used to associate scores with question IDs
sub (paginated list): The submission object that contains the questions / scores / comments
Returns True if the program succeeded in posting the scores and comments. False otherwise
"""
update_obj = create_questions_update_object(scores, questions, question_values, include_partial)
# This will fail if someone is taking the test; even though there is no submission, their attempt is assigned a submission number which lets me get to this point
try:
update = sub.update_score_and_comments(quiz_submissions=update_obj)
return True
except:
return False
def create_questions_update_object(scores, questions, question_values, include_partial):
"""
Creates an object compatible with the Canvas API to post individual question grades
Args:
scores (list): A list that contains the scores pulled from autograder for the student's submissions. These should be in the order of the question numbers
questions (paginated list): The canvasapi object that contains the questions the student answered. Used to associate scores with question IDs
question_values (list): The point values for each question
include_partial (bool): Whether or not to assign a grade to questions that did not receive 100%
Returns True if the program succeeded in posting the scores and comments. False otherwise
"""
#Get the list of questions IDs for the API
# First try for when questions is a full list
q_ids = [str(x['question_id']) for x in questions]
# temp dictionary for the final dictionary
new_dict = {}
# Remove problems that aren't 100% so that we can manually grade
# The 86.66 was to accommodate the Q7 on PA2 from Fall 2023, which we were able to autograde
# scores = [score if score == 100 else 0 for i,score in enumerate(scores)]
# Now put that dict in another dict, because API
# For the purposes of this class, we want to only grade those questions that received 100%, because
# we manually grade all others. This allows instructors to easily see what isn't graded
# by dropping scores from the eventual API call
i = 0
for spec_quids,spec_scores in zip(q_ids[:len(scores)],[score if score != "None" else 0 for score in scores]):
if(include_partial):
# Include all scores
new_dict[spec_quids]={"score":spec_scores, "comment":""}
elif(spec_scores / question_values[i] == 1):
# Include only scores that received full credit
new_dict[spec_quids]={"score":spec_scores, "comment":""}
i += 1
# And the final dict in a dict in a list, because API
update_obj = [
{
"attempt": 1,
"fudge_points": 0,
"questions": new_dict
}]
return update_obj
def update_submission_comment(user_id, comment_id, updated_comment, assignment_id, course_id):
"""
Updates a single comment on an assignment submission
Args:
user_id (str): A specific student's Canvas ID to find the appropriate submission
comment_id (str): The id number of the comment within the submission object
updated_comment (str): A single string that will be added as a comment for the student to see on their submission
assignment_id (str): The ID number of the assignment that contains the comment. Usually PA_TO_GRADE
Returns True if the program successfully updated the comment. False otherwise
"""
url = "https://canvas.instructure.com/api/v1/courses/{}/assignments/{}/submissions/{}/comments/{}".format(course_id, assignment_id,user_id, comment_id)
params = (('access_token', API_KEY), ('comment', updated_comment))
result = requests.put(url,params=params)
if(result.status_code == 200):
return True
return False
Community helpTo interact with Panda Bot, our automated chatbot, you need to sign up or log in:
Sign inTo interact with Panda Bot, our automated chatbot, you need to sign up or log in:
Sign in