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!
Goal: Given a student's name and email address, I'd like to get them added to a course using the Canvas API and Python. For my purposes, either creating a Canvas account for them or sending them an invitation email to self register would be acceptable. I am using a Canvas Free for Teacher account and am the instructor for this course with a valid and working API token.
Problem: I have yet to find the a proper API endpoint and data structure that yield any positive results for either of the two options above. The API documentation is rather vague, and I am unable to find my account ID parameter (perhaps because I'm on a free account?) that would be needed for the request structure in the documentation.
Attempts: I have seen several older threads all with different solutions. For all attempts, here's what my header structure looks like.
api_token = token goes here
c_headers = {
'Authorization': f'Bearer {api_token}'
}Here's what I've tried so far.
The option below yields a 400 response...
url = "https://canvas.instructure.com/api/v1/accounts/self/users"
data = {
'user[name]':'Johnny Doe',
'user[short_name]':'John Doe',
'user[sortable_name]':'Doe, Johnny',
'pseudonym[unique_id]':'johndoe@johndoe.com'
}
response = requests.post(url, headers=c_headers, data=data)...and this one below also returns 400.
url = "https://canvas.instructure.com/api/v1/accounts/self/users"
data = {
'name':'Johnny Doe',
'short_name':'John Doe',
'sortable_name':'Doe, Johnny',
'unique_id':'johndoe@johndoe.com'
}
response = requests.post(url, headers=c_headers, data=data)This option below with a different data structure yields a 500 response.
url = "https://canvas.instructure.com/api/v1/accounts/self/users"
data = {
'user': {
'name': 'Johnny Doe',
'short_name': 'John Doe',
'sortable_name':'Doe, Johnny'
},
'pseudonym': {
'unique_id': 'johndoe@johndoe.com'
}
}
response = requests.post(url, headers=c_headers, data=data)
Ultimately, I just need a solution that I can feed a name and email so that a student will get automatically added or invited to my course once they complete an external registration form, so I am open to other suggestions or workflows that achieve this same result.
Thanks!
Solved! Go to Solution.
Hi @cpesther,
One more thing to perhaps try to add after reviewing the API more...
'user[terms_of_use]'=True
It may end up being that you won't be able to use the API for user creation with the free-for-teacher instance, but I haven't tried it myself (I also know that behind the scenes there are some different configs on different free-for-teacher subaccounts, which complicates trying to test and replicate results).
I moved the post to the developers area of the community, where some API experts tend to hang out, but I could also move it over to the free-for-teacher area later if needed.
-Chris
Okay, the combination of y'all answers was the solution! Here's all the details for future folks:
Solution: To create a Canvas account for a student using the Canvas API on the Free for Teachers plan, go through the following steps.
This process creates an account in the Canvas system for the student, but doesn't yet add them to your course (that comes later). Use the following structure for your request.
# If on Free for Teacher plan, use '10' for the account ID parameter
account_id = 10
account_url = f"https://canvas.instructure.com/api/v1/accounts/{account_id}/users"
account_data = {
'user': {
'name': 'Johnny Doe',
'short_name': 'John Doe',
'sortable_name':'Doe, Johnny',
'terms_of_use':True # Required
},
'pseudonym': {
'unique_id': 'johndoe@johndoe.com',
'force_self_registration':True # Required to send email
}
}
response = requests.post(account_url, headers=c_headers, json=account_data)Once this response is successful, a Canvas account will have been created for this user and they should receive an email asking them to confirm and complete registration. In the text response to this request will be the ID number; this is important if you want to enroll the user in a course in the next step.
If the email address you provided is already associated with a Canvas account, you will receive a 400 error and the response will include a message indicating that the email already belongs to a user. You'll need to implement some other solution in your code for when/if this occurs.
Now that the student has an account, the next logical step is to go ahead and enroll them as a student in your course. The two lines of code below are an example of how to extract the user ID number from the response to account creation above. Your code will vary.
new_user_data = json.loads(response.text) # Convert string return into a dict
new_user_id = new_user_data.get('id') # Get the ID number of the new userNow that we have the ID number for the new user, we can put together an API request to enroll them in the course, which should be structured as seen below. You can most likely format the data for this request in the json-esqe format used for the previous one, but this is just the structure that happened to work for me. Make sure to replace the {course_id} argument with the ID number for the course in which you want to enroll the user.
# Package up all of the enrollment request data
course_id = 0000000000
enroll_url = f'https://canvas.instructure.com/api/v1/courses/{course_id}/enrollments'
enroll_data = {
'enrollment[user_id]':new_user_id, # Required
'enrollment[type]':'StudentEnrollment', # Required
'enrollemnt[notify]':True,
'enrollment[enrollment_state]':'active'
}
# Make the request to the API
enroll_response = requests.post(enroll_url, headers=c_headers, data=enroll_data)Setting the enrollment state to active means that they won't be listed as a pending student in your roster, if that is your preference. Listing a student as active is also useful (at least in my case) since their email address will then be associated with their student profile, which is not the case for pending students, for whatever reason. This is helpful if you need to compare an external list of names and emails to the ones already enrolled in the course to prevent repeat/excess enrollment requests. It should also help save the student a step of needing to accept an invitation.
Thanks so much for everyone's help!
Hi @cpesther,
I think you may need to add 'pseudonym[force_self_registration]':True to your data dictionary for example #1 on the free-for-teacher instance so the user will get the registration email to create their password and everything.
For example #3, the data format you're using there looks to be JSON, so on your requests call, you'd change data=data to json=data and I think that should then yield the same result as call #1 (to get it to be successful, you'd need to add 'force_self_registration':True to the 'pseudonym' dictionary there too).
Let us know if this works!
-Chris
Hi @chriscas,
Thanks for the quick reply! Unfortunately, adding that argument did not resolve the issue, and the modified examples still yield the same respective error codes. I tried formatting the True argument both as a Python boolean value and as a string to no avail. Switching the example three request over to providing the data variable into the json argument causes it to return a 400 error code, the same response as example one, instead of 500.
Here's what my new example one request looked like with the added value.
url = "https://canvas.instructure.com/api/v1/accounts/self/users"
data = {
'user[name]':'Johnny Doe',
'user[short_name]':'John Doe',
'user[sortable_name]':'Doe, Johnny',
'pseudonym[unique_id]':'johndoe@johndoe.com',
'pseudonym[force_self_registration]':True
}
response = requests.post(url, headers=c_headers, data=data)I also tried using a couple different email addresses, one of which I know has never been associated with a Canvas account, just to make sure the API wasn't running into trouble by trying to make a new account for an email address that is already associated with an existing one. This didn't change the responses received.
Thanks!
Hi @cpesther,
One more thing to perhaps try to add after reviewing the API more...
'user[terms_of_use]'=True
It may end up being that you won't be able to use the API for user creation with the free-for-teacher instance, but I haven't tried it myself (I also know that behind the scenes there are some different configs on different free-for-teacher subaccounts, which complicates trying to test and replicate results).
I moved the post to the developers area of the community, where some API experts tend to hang out, but I could also move it over to the free-for-teacher area later if needed.
-Chris
Okay, the combination of y'all answers was the solution! Here's all the details for future folks:
Solution: To create a Canvas account for a student using the Canvas API on the Free for Teachers plan, go through the following steps.
This process creates an account in the Canvas system for the student, but doesn't yet add them to your course (that comes later). Use the following structure for your request.
# If on Free for Teacher plan, use '10' for the account ID parameter
account_id = 10
account_url = f"https://canvas.instructure.com/api/v1/accounts/{account_id}/users"
account_data = {
'user': {
'name': 'Johnny Doe',
'short_name': 'John Doe',
'sortable_name':'Doe, Johnny',
'terms_of_use':True # Required
},
'pseudonym': {
'unique_id': 'johndoe@johndoe.com',
'force_self_registration':True # Required to send email
}
}
response = requests.post(account_url, headers=c_headers, json=account_data)Once this response is successful, a Canvas account will have been created for this user and they should receive an email asking them to confirm and complete registration. In the text response to this request will be the ID number; this is important if you want to enroll the user in a course in the next step.
If the email address you provided is already associated with a Canvas account, you will receive a 400 error and the response will include a message indicating that the email already belongs to a user. You'll need to implement some other solution in your code for when/if this occurs.
Now that the student has an account, the next logical step is to go ahead and enroll them as a student in your course. The two lines of code below are an example of how to extract the user ID number from the response to account creation above. Your code will vary.
new_user_data = json.loads(response.text) # Convert string return into a dict
new_user_id = new_user_data.get('id') # Get the ID number of the new userNow that we have the ID number for the new user, we can put together an API request to enroll them in the course, which should be structured as seen below. You can most likely format the data for this request in the json-esqe format used for the previous one, but this is just the structure that happened to work for me. Make sure to replace the {course_id} argument with the ID number for the course in which you want to enroll the user.
# Package up all of the enrollment request data
course_id = 0000000000
enroll_url = f'https://canvas.instructure.com/api/v1/courses/{course_id}/enrollments'
enroll_data = {
'enrollment[user_id]':new_user_id, # Required
'enrollment[type]':'StudentEnrollment', # Required
'enrollemnt[notify]':True,
'enrollment[enrollment_state]':'active'
}
# Make the request to the API
enroll_response = requests.post(enroll_url, headers=c_headers, data=enroll_data)Setting the enrollment state to active means that they won't be listed as a pending student in your roster, if that is your preference. Listing a student as active is also useful (at least in my case) since their email address will then be associated with their student profile, which is not the case for pending students, for whatever reason. This is helpful if you need to compare an external list of names and emails to the ones already enrolled in the course to prevent repeat/excess enrollment requests. It should also help save the student a step of needing to accept an invitation.
Thanks so much for everyone's help!
This was so helpful. Thank you so much for sharing with us! I have run into the potential issue mentioned in the solution. There is an email that is already associated with an account, so it is invalid. Unfortunately, I cannot seem to use the 'list user' endpoint to find the user's ID based on email address. It is returning a 403 error. I am guessing this is a security feature so that it doesn't return personal information just from an email. I also tried searching a known user ID (as opposed to by email) and it returns the same. Is there a solution within the API, or did you just come up with an external solution like manually inviting them?
There was no Canvas-side solution that I found (although I must admit I didn't look that hard).
I worked around this problem by designing my code such that if adding the student fails for any reason my backend (mostly AWS Lambda and a couple Dynamo DBs) sends them an email with instructions on how to manually login to Canvas with their existing email address (or to create a new account) along with the course invitation code so that they can join manually. My system also CCs my account on these emails so that I'll know if any error occurred and can keep an eye on it as well.
As a last safety net, I have a function that runs every couple hours and compares the list of students in my course with those that have completed registration (on an external platform) and if it finds anyone who has slipped through the cracks, it sends me a email so I can address it.
Hope this helps!
Thanks for the speedy reply, and your insights! 😁
Hi @cpesther,
In your question you said that you were looking for an "account ID". Saying "account ID" could reference a few different possibilities but you might be referring to the (sub)account ID that the course itself is in.
I have three different courses in FFT, each of them created at different times (according to their API details: May 2018, November 2024, and March 2025) and when I go into the "Settings" area for each of them, they all have "Free for Teacher" listed as the subaccount and its clickable (but with an error) link is https://canvas.instructure.com/accounts/10.
I believe that means its account ID is 10 and it does not appear (at least not that I can see) to have an SIS ID to identify the account.
Along with @chriscas' information, I hope you find this helpful.
Let us know how it goes.
-Doug
Hi @dbrace,
I do recall seeing an account ID listed as 10 buried somewhere in some test requests to the API. However, replacing the "self" term in the API URL with "10" sadly doesn't change anything. With your solution the API request is now formatted exactly as the documentation outlines, which is why I'm very confused that it's still unhappy with what I'm sending.
Thanks!
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