// 06 — 2025
Canvas Downloader
Archiving course files that only exist as links inside page HTML
- Role
- Sole developer
- Stack
- Python
- Year
- 2025
- Status
- Works
- Licence
- MIT
// Why it exists
I had my own lecture notes written up, but I wanted the source material as well — to feed the knowledge base behind Hal, which files anything dropped into its inbox into a cross-linked, indexed Obsidian vault. Course material is exactly the kind of thing worth being able to search four years later, and exactly the kind of thing Canvas quietly deletes when a course is unpublished.
So this walks the Canvas REST API with a personal access token and pulls everything down in one pass — lecture slides, PDFs, page text and linked handouts — into tidy folders sorted by term and course, with a Course structure.md per course so you can tell what it covered a year later.
// The one part that isn't obvious
Pulling the Files tab is a paginated API call, and that part is dull. The problem is that a lot of material never appears in the Files tab at all. Lecturers link files inline in page text, assignments and announcements — a bare <a href="/courses/123/files/8834514">slides</a> in the page body — and that file may not be listed under Files, may live in a folder you can’t browse, and is the most likely thing to disappear.
So every page, assignment and announcement is fetched, /files/<id> references are extracted from the HTML, and each one is resolved separately through the API. In practice they’re a meaningful share of what a course actually contains.

// Current state
Works — it does the job it was written for, and has been run against a full multi-year account.
Honest about scale: a single-file script written in an afternoon, with no tests, no packaging and no flags beyond the two constants at the top. That’s proportionate to what it is — the interesting part isn’t the code, it’s that it completes the Hal ecosystem.
Known rough edges: Canvas deployments vary a lot. Institutions disable endpoints, rename things and set different permissions, so an instance that behaves oddly is likely and worth an issue.