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!
I am attempting to delete announcements via an API call. I have a PHP script that allows me to pull a list of courses from a csv file. From that list of courses, I would like to make an API call that deletes all announcements. From the API documentation, it seems like I the call I would need to make is
However, I would first need to know which topic_ids are announcements. That being the case, would I first need to pull a list of all announcements, and filter out only announcements via the following with the parameter only_announcements set to true?
If I do this, I'm guessing I would need to store these results in an array and then make the delete call after that.
Coding is not my primary area of training, so I might be missing something basic here. If anyone has any thoughts or suggestions, or example code of similar operations, this would help greatly.
Thanks!
Nick
Hi there,
Not sure if you arrived at a solution yet, but you can just get the announcements in just one API call without having to filter them. As you mentioned you can pass the "only_announcements=true" argument in your query string, which should only return announcements and no discussions.
GET https://school.instructure.com/api/v1/courses/:course_id/discussion_topics?only_announcements=trueYou can store those announcements in an array, then loop over them (you will only need the ID attribute), then pass the array to the DELETE call.
I have made a simply python program which does what you want, details and access to the code via: Deleting announcements: Chip sandbox . Note that in addition to the course_id you can also provide a starting and ending date, so that it is possible to be selective in what you delete.
This script is really cool! I'm not familiar with creating json configurations, do you have an example config file for this program?
Thanks!
The file looks like this:
{
"canvas":{
"access_token": "xxxx",
"host": "school.instructure.com"
}
}
I like having a separate configuration file so that I do not have to put the access token into each program and I can also easily change the host between the production, test, and beta instances.
Thanks. I'm going to start doing this for all the scripts!
I also noticed that the program doesn't open the modules.csv. Should there be a snippet that opens this file?
No, this is an old piece of code that can be removed - as this line of code about modules.csv is irrelevant in this code. Sorry for the confusion.
I might be missing something very obvious, but I'm not seeing how you pass in the course id to the functions. I've tried defining a single course id at the top of the program and I receive the "Insufficient Arguments" prompt when I execute the program. Is there another location where I can add the course_id?
This is due to the use of the library "optparse". This setup occurs to parse the command line argument:
parser = optparse.OptionParser() # create a parser for the command line
parser.add_option('-v', '--verbose', # add an option
dest="verbose",
default=False,
action="store_true",
help="Print lots of output to stdout"
)
options, remainder = parser.parse_args() # set "options" to the set of options that were specified
# set remainder to the remaining arguments from the command line
The course_id is given as an argument on the command line. It gets picketed up with the following code:
course_id=remainder[0]
Then this is passed to a function to get the list of announcements, with:
output=list_announcements(course_id)
Once you figure out the ids of the announcements to delete, you delete them by calling:
delete_announcement_from_course(course_id, announcement_id)
I tweaked the code a bit to change the input to a csv file so that you can delete announcements for many courses at once. Gerald, if you don't mind, I can share that here.
I have one further challenge however. I noticed that you're using ?context_codes[]=course_%s in order to identify the course. However, we do everything using the sisid. In the api documentation, Canvas LMS REST API Documentation , I see "List of context_codes to retrieve announcements for (for example, course_123). Only courses are presently supported." I need to adapt the script to identify courses using sis_course_id, as seen here, Object IDs, SIS IDs, and special IDs - Canvas LMS REST API Documentation . I tried the following but it doesn't return anything:
/api/v1/courses/sis_course_id:A1234/announcementsDo you know if this is possible?
Thanks again for all your help!
Feel free to share the code as widely as you want.
Based on a quick look at the code (such as app/models/user.rb) there seems to be an assumption that the context_code has the form "course_" followed by digits - given statements of the form:
course_ids = context_codes.grep(/\Acourse_\d+\z/).map{ |s| s.sub(/\Acourse_/, '').to_i }
similarly app/coffeescripts/bundles/wiki_page_show.coffee shows:
axios.get("/api/v1/announcements?context_codes[]=course_#{ENV.COURSE_ID}&per_page=#{ENV.ANNOUNCEMENT_LIMIT || 3}&page=1&start_date=1900-01-01&end_date=#{new Date().toISOString()}&active_only=true&text_only=true")
The definitive answer seems to be in app/controllers/announcements_api_controller.rb where it says:
def parse_context_codes
context_codes = Array(params[:context_codes])
if context_codes.empty?
return render :json => { :message => 'Missing context_codes' }, :status => :bad_request
end
@course_ids = context_codes.inject([]) do |ids, context_code|
klass, id = ActiveRecord::Base.parse_asset_string(context_code)
unless klass == 'Course'
return render :json => { :message => 'Invalid context_codes; only `course` codes are supported' },
:status => :bad_request
end
ids << id
end
end
As near as I can tell the only way to do what you want is to first do the mapping from sis_course_id to course_id, then use that course_id with the query.
That's what I was thinking. I'll look around for a script that converts course_id to sis_course_id.
I uploaded the edited script here: https://my.uclaextension.edu/courses/13642/pages/delete-announcement-script . Let me know if you have any comments or suggestions.
Check out the program export-list-of-all-courses-with-account-info.py available from my git hub (https://gitr.sys.kth.se/maguire/Canvas ) or my sandbox page
Courses and accounts: Chip sandbox columns L and W give you the information that you are looking for.
Unfortunately, I cannot access the URL as I get prompted to login to the UCLA extension courses Canvas web site.
Hmm strange. Does a link to the course home work? Nick Sandbox
Yes, the link to Nick Sandbox works, but not the link to the page: https://my.uclaextension.edu/courses/13642/modules/items/602190 both this an the URL you originally sent take me to Log In to Canvas
Hi Gerald,
Try again - there might have been some lag when making the course publicly available. It's working for me in incognito at least... If doesn't, I think this link will work: delete-all-course-announcements.py - Box
It now works for me. Thanks!
@nschwiet , I'm pretty sure I talked with you at a hack-day event at InstructureCon a couple of years ago. If I remember right, you were just starting out and thinking that you wanted to code in Python based off things you had seen. I didn't know anything about Python (still don't) and so we probably talked PHP more -- but that could be someone else.
Anyway, reading your problem, it looks like you've strayed away from the original intent -- perhaps because of the fine work @maguire is doing and because you're not sure of what you're doing or just because of a desire to learn more.
If you use the call that llawson mentioned, you can get the course ID by parsing some of the html_url properties. That API call should respond to the SIS codel, return the list of announcements with their IDs, and give you the course ID through a parse.
Here is a portion of what is returned when I make a call to my sandbox, which has some announcements.
GET /api/v1/courses/sis_course_id:sb_james/discussion_topics?only_announcements=1The announcement ID is 1120683 and the course ID can be picked out by matching a regular expression on /courses\/([0-9]+)/ and then it's the first match.
You had originally stated that you wanted to remove all of the announcements. I'm not sure if that's what you're still after, but if that's what someone was trying to do, they wouldn't have to use the Announcements API at all, which means that you wouldn't need the course ID -- you could just use the sis_course_id and you don't have to mess with context ids at all, either.
You could then use the original API call you mentioned to delete the topics.
It seems you may be headed down a path of learning some really cool things, which seemed to be what was happening when we talked at InstructureCon. Sometimes it's cool to just absorb and I love doing that myself, especially if I can find someone willing to help. If you're just trying to delete the announcements, Parsa's answer was the one that you needed and then it's short trip to being done.
@James , I hope that I have not lead @nschwiet astray. I should state that part of my bias for Python versus PHP is due to the fact that using Python users can run scripts from their own computers (be this be a linux, MacOS, or Windows machines) - while locally the ability to run PHP on university computers is very restricted.
While your proposed method of using regular expression matching on the URL works, I would be a bit concerned about the fact that it is dependant upon the implicit knowledge that this is the course_id, hence if there were a change in the URL scheme this would break and be very hard to figure out later. However, I do agree that if the goal is simply to delete all of the announcements for a course given the sis_course_id that your method is better.
Of of the things that I find very interesting about Nick's approach is that they are using sis_course_ids. One of the advantages that this offers is that these IDs are more useful to humans than the usual Canvas course_id which might just as well be a 256-bit long hash value. There is currently a local debate about retaining one of the former web systems to couple between the database that contains the official course catalog which contains course codes of the form two letters and four digits (where the first digit encodes the level of the course) and a web page which couples courses and course instances (i.e., an instance of a course taught in a specific year, term, etc.). One of the possibilities would seem to be to have Canvas courses with a sis_course_id that is identical to the course code. This would make it easy to have a URL of the form:xxx.instructure.com/courses/sis_course_id:IK1552 for a Canvas course with the "top level" course description and then have URLs on a page in this course that point to a specific instance of this course, such as xxx.instructure.com/courses/sis_course_id:IK1552VT171/ (By the way I am really happy to see that a URL of this format actually works when entered into the browser).
@maguire , I like shooting the breeze with you; it's always insightful. I think what you're doing with Nick is great and that he's probably loving it; please don't read anything else into that.
I would advocate people use whatever language they're most familiar with rather than trying to learn a new one unless there's a compelling reason like they can't do that in their language. I wasn't trying to sway him back to using PHP; I was looking at what he started with and where he was at now and there was a huge disconnect that didn't have to be there as it looked like he was one API call away from having what he wanted.
You can load PHP on the desktop, but I run my stuff through the Eclipse editor and very little from the command line in Windows. In Linux, I use command line for some PHP stuff, but it's mostly when I need to write a web interface to go with it. I've got NodeJS setup in a similar fashion, but used the other direction: I use NodeJS within Eclipse when I don't want to keep working within the browser (like a script that downloads a large amount of data) and use the JavaScript built into the browser when I'm testing live things. I probably even have Python installed because something I'm using needed it.
The API calls are, for the most part, complementary to the the routes in Canvas, so I wouldn't be worried about them changing anytime soon. If they do, a lot more than Nick's script is going to break as almost everything I've written that needs the course ID gets it that way and I even advertise in my Google Scripts that you can even paste in the URL and I'll figure out what the course code is for you. What may happen is that Canvas decides to add support for global announcements or totally rethink the way communications are done. Then it might change, but that might also end up using a new API as well.
If Nick builds off what he had and adds the only_announcements query parameter, he wouldn't need the context ID, so it wouldn't be necessary for getting the Canvas course ID (as opposed to the SIS course ID), so the discussion about how to get it becomes unnecessary. If I was going the course IDs based off the SIS course ID, I wouldn't do it this way -- I was trying to look for the most efficient way to get the information.
I keep a local database that has the Canvas ID and the SIS ID for all of our stuff. If I was in Nick's position, I would still make the two calls to Discussion Topics API and ignore the Announcements API, but if I was forced to use the Announcements API for some reason (like limiting the data before the data was sent), I would already have that Canvas course ID available to me because of that. Our SIS doesn't have a single primary key for the courses or sections, so our SIS codes are a useful human-readable hash like sp17-math_113-01, but we don't do any direct linking into courses within Canvas, just to the dashboard.
I'm not sure who many of you are aware of this, but if you are an Admin and have and admin token Canvas gives you a way to test out all of their API's without ever having to write any code and you can test those API's against any of your environments.
https://YourInstitutionHere.instructure.com/doc/api/live -- Production
https://YourInstitutionHere.beta.instructure.com/doc/api/live -- Beta
https://YourInstitutionHere.test.instructure.com/doc/api/live -- Test
I use this quite often to explore things; however, I have found that some of the functionality is broken via this interface (just as it is broken in the grade book) - see for example, https://community.canvaslms.com/thread/14978-changing-limit-on-the-number-of-custom-columns-shown-by... (This problem still seems to be in the Canvas beta.)
For those who are interested, I've updated the Canvas page with the script adapted to read a csv file that is populated with course sisids: Delete Announcement Script: Nick Sandbox
Thanks Gerald for offering your original script, and James for pointing me in the correct direction with the sis_course_id. James, I remember speaking with you at InstructorCon! I've worked on PHP since then and worked on a script to copy courses using PHP. I'm not trained as a programmer, so sometimes working through these things is a bit challenging (figuring out a language and API simultaneously), but fun!
I think my next step will be to combine the two scripts, I just need to determine which language to use.
Thanks again all for the help!
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