canvasser
Automation for Canvas (*.instructure.com) that navigates the site as a person would,
rather than through the REST API. Built against UF and running at three institutions; most
need only one setting, an SSO path (see
Scope). A provider whose sign-in form differs
needs a row in auth.IDPS as well — there are two such rows today, Shibboleth and
Microsoft Entra ID.
That choice is deliberate. UF discontinued API token support after Instructure began capping non-admin tokens at 30 days, and advises retiring token-based scripts. Browser automation is the better-supported path for this institution.
Status: in use. A full semester of dates for a real course — 37 assignments, set and cleared — pulled to CSV, edited in a spreadsheet, and written back, ending in
No changes. 37 sheet row(s) match Canvas exactly.Plain assignments and classic quizzes both. Writing is opt-in (--commit), and every field is checked three times: the typed values are read back out of the form before saving, Canvas's own error messages are read after, and the stored value is re-read from the page's state afterwards.Assignment settings — points, grading type, submission types, allowed attempts, peer review, publish state — are pulled to a second CSV. Writing them is newer than the date path: points, grading type, submission types, allowed attempts and peer review can be written, and a title with
--rename. Publish state can be turned on — on a brand-new assignment or one that already exists — but not off: Canvas offers a "Save & Publish" button precisely while an assignment is unpublished, and no control at all for the other direction. Anything not writable is reported rather than silently ignored.New assignments can be created from the settings sheet: put
NEWin the id cell and the id Canvas assigns is written straight back into it, so the row becomes an ordinary edit and a second push cannot create a duplicate. Assignments only — quizzes and discussions are refused, since Canvas makes each of those somewhere else. There is no delete command, so anything created by mistake has to be removed in Canvas by hand.Not supported: assignments with per-student or per-section overrides. Saving Canvas's edit form submits every date card, so getting that wrong deletes an accommodation date. It is refused rather than attempted — for settings writes too, since they save the same form.
What it does
A round trip: pull to CSV, edit in a spreadsheet, push it back.
canvasser status # is the stored session still authenticated?
canvasser login # authenticate (SSO, plus a second factor if your
# institution uses one)
canvasser courses # list courses, with ids
canvasser settings 580777 # all five displays; narrow with --sections etc.
canvasser pull 580777 # dates AND settings -> two CSVs
canvasser pull 580777 --dates # just dates-580777.csv
canvasser pull 580777 --info # just info-580777.csv
canvasser push dates-580777.csv # show what would change; writes nothing
canvasser push dates-580777.csv --commit # actually write it
canvasser push info-580777.csv --commit # settings, and any NEW rows
canvasser push info-580777.csv --rename # also allow title changes (still needs --commit)
canvasser push info-580777.csv --only 7289071 # just that row; repeat --only for more
canvasser install-browser # fetch Chromium up front (usually automatic)
canvasser --institution templeu status # a different Canvas, with its own session
canvasser --institution ucf --passwordless login # no password: approve in your app
push previews by default. Running it repeatedly while editing a sheet cannot touch the
course; only --commit writes.
push works out which kind of sheet it was given by reading the file's first row, not
its name — so push info-580777.csv reaches the settings path even if you rename the file.
--dates and --info on push are assertions rather than switches: they refuse a file
that says it is the other kind.
Both sheets come from the same page loads, so asking for one is no faster than asking for both.
Reading a course means loading one page per assignment, so pull and push show a progress
bar while they work. Piped or redirected, they print a plain numbered list instead — no
escape sequences, so a captured log stays readable. Set CANVASSER_ASCII=1 to force plain
ASCII drawing on a terminal that cannot render box characters.
Requirements
-
Python 3.10+ (developed and tested on 3.13)
-
A Canvas account at an
*.instructure.cominstitution. Verified against a live login at three: UF (Shibboleth SSO with Duo MFA), Temple (Shibboleth, no second factor), and UCF (Microsoft Entra ID, passwordless — approve in Microsoft Authenticator). Each institution needs an SSO path, which has no safe default and must be configured; beyond that, the only institution-specific code is a row inauth.IDPSnaming the identity provider's form. Vanity domains are not supported — UCF's Canvas must be reached asucf.instructure.com, notwebcourses.ucf.edu.Sign-in adapts to what the provider asks for. Entra tenants often default to a passkey, which this tool cannot answer (the credential lives in hardware); it declines truthfully and takes whatever else is offered — a password, or an authenticator app. Whether a password is wanted at all is the provider's decision, not a setting here. Use
--passwordlessto prefer the app even when a password is on offer, and--factor passcodeto type a code instead of approving a push. One-time codes are always prompted for, never stored: automating them from a stored seed would put both factors in one file. -
No display required — runs headless
Install
pip install canvasser
That is the whole install. The first command that needs a browser will notice one is missing and offer to fetch it:
Chromium is not installed; canvasser cannot drive Canvas without it.
Download it now (~150 MB, one time)? [y/N]
Answer y and it continues into the command you asked for. To do it ahead of time, or in a
script, run canvasser install-browser (add --with-deps on Linux to pull the system
libraries Chromium needs; that part needs root).
Why there is a download at all. The playwright package on PyPI ships the automation
library and its driver, not the browser — browser builds are large platform-specific native
binaries, not Python, so no PyPI package carries them. Playwright pins an exact build per
library version, which is the whole reason page behaviour is reproducible.
The browsers land in a shared per-user cache (~/.cache/ms-playwright and equivalents),
so it is once per machine rather than once per virtualenv — verified under plain venvs,
pipx, uv tool install, and throwaway uvx environments. Nothing is downloaded without
asking: with no terminal attached (cron, CI, a pipe) canvasser declines and prints the
command instead.
From a checkout instead
python3 -m venv .venv
./.venv/bin/pip install -e .
./.venv/bin/python -m canvasser install-browser --with-deps
Where it keeps things
The saved session, browser profile, optional secrets file, and any debug snapshots share one per-user directory — never the working directory, so nothing credential-bearing can be committed by accident. (Snapshots render whole Canvas pages, so they can contain student data; the directory is created mode 700.)
%LOCALAPPDATA%\canvasser |
Windows |
~/Library/Application Support/canvasser |
macOS |
$XDG_STATE_HOME/canvasser |
otherwise — usually ~/.local/state/canvasser |
Set $CANVASSER_HOME to put it somewhere else; that is the only thing that overrides the
platform default. Use it to keep state on a durable or encrypted volume.
For a second institution, use --institution <subdomain> rather than a second
$CANVASSER_HOME: it keeps that Canvas's credentials, session and browser profile in a
subdirectory of the same state directory, so the two cannot overwrite each other's login.
The account you already have stays exactly where it is.
Credentials
Resolved per field, first source that has them wins:
--secrets-file PATH(--usernamesupplies the name)$CANVAS_USERNAME/$CANVAS_PASSWORDcanvas.envin the state directory above (mode600)- an interactive prompt
The former $GATORLINK_USERNAME / $GATORLINK_PASSWORD spellings are still read, so an
existing canvas.env keeps working; -v reports them as deprecated.
There is deliberately no --password flag. OpenSSH is the model: argv is not private —
it lands in shell history and /proc/<pid>/cmdline is world-readable. For scripted runs,
sshpass answers the prompt, exactly as it does for ssh:
sshpass -f ~/.canvas-pw canvasser --username jjb pull 580777
Prompts read /dev/tty, not stdin, so this works even when stdin is a pipe. --no-prompt
disables prompting entirely for unattended use; -v (or --verbose) reports which source
each value came from, never the value itself.
--headed runs the browser visibly instead of headless, on a machine with a display.
It is the escape hatch for a sign-in canvasser cannot complete on its own — a hardware
security key, or a passkey that needs a QR scanned from the browser's own dialog. Finish it
by hand in that window and the session is saved exactly as it would be otherwise.
Second factors
Have your phone in hand when you run login. What you are asked for depends on the
institution, and canvasser prints whatever the page shows so you can act on it — the number
to match exists only inside a headless browser otherwise. It is framed in a box at both
providers, because it arrives in a window of about a minute and a line of ordinary text is
easy to scroll past. If the number cannot be read off the page, the box still appears with
the instruction, and the page's own words are printed below it.
| what appears | ||
|---|---|---|
| Duo (e.g. UF) | a number to tap in the Duo app | re-read every 5s in case the push is re-sent |
| Microsoft Authenticator (e.g. UCF) | a number to match in the app | the page polls until you approve |
| none (e.g. Temple) | nothing | the login simply completes |
--factor passcode types a 6-digit code instead of approving a push — it works for Duo and
for Microsoft Authenticator.
If you receive a push you were not expecting, deny it.
Your MFA stays intact: a stored password is something you know, the phone is something you have. No TOTP seed is ever stored — one-time codes are prompted for and forgotten, because keeping the seed beside the password would put both factors in one file. Once the provider remembers the device (Duo: ~10 hours), runs need no interaction at all.
Selecting a course
courses and pull share three independent scope axes:
| Axis | Default | Flags |
|---|---|---|
| enrollment | both active and archived | --active / --archived |
| publish state | any | --published / --unpublished |
| favorite | favorites only | --favorite / --unmarked |
--all opens all three axes at once — both sides of each, the widest possible scope.
It therefore does not compose with a narrowing flag: --all --published is every course,
not every published one, because --all has already named --unpublished too.
Naming both sides of an axis unions them — --active --archived is every enrollment,
which is what the words say. Favorites is the one deliberately narrowed default — it is
the list you curate in Canvas
itself, via the star on the Courses page. Canvas's own "Current Enrollments" is not a useful
definition of current: on a long-lived account it is mostly sandboxes and dev shells.
pull takes a course id or a name fragment, and an ambiguous fragment is an error rather
than a guess. Omit it entirely for an interactive picker:
canvasser pull 580777
canvasser pull "Comp Engr Design"
canvasser pull --course-file ~/current-class.txt
CANVASSER_COURSE=580777 canvasser pull
canvasser pull # numbered picker
The datesheet CSV
# canvasser datesheet v3.1,,course=580777,timezone=Eastern Time (US & Canada),,,,iana=America/New_York,,
Assignment Details,,,,unlock_at,,due_at,,lock_at,
assignment_id,override_id,title,assign_to,open_date,open_time,due_date,due_time,close_date,close_time
7289050,,01 - Equipment Demonstration,Everyone,2026-08-21,00:00,2026-08-28,23:59,2026-09-02,23:59
Three date fields, each split into a date column and a time column so a spreadsheet can
bulk-shift them: open_* is Canvas's unlock_at ("Available from"), due_* is due_at,
close_* is lock_at ("Until").
- Only
assignment_idis required. Delete any other column, any row, and both preamble rows — the file still reads. Columns are matched by name, so order does not matter either. - An absent column means "leave this alone"; an empty cell means "clear it". Deleting the
close_*columns will not wipe your lock dates. - Rows are matched on
(assignment_id, override_id), never on title — renaming an assignment in the spreadsheet cannot retarget a write. - The timezone is declared once, in row 1, twice over:
timezone=is Canvas's familiar name for people,iana=is the identifierpushresolves wall clocks through. Values themselves carry no offset. - The declared zone says what the sheet's own times are written in — nothing more. If it
is not the course's zone,
pushconverts every value into course time before comparing or writing, preserving the instant, and prints what it did. A sheet edited in Tokyo saying2026-08-29 12:59and a New York course holding2026-08-28 23:59are the same deadline. iana=wins, buttimezone=is a real fallback. Canvas is Rails, soEastern Time (US & Canada)is anActiveSupport::TimeZonename and maps to an IANA zone; if you deleteiana=the friendly label still resolves. All 153 of Canvas's labels are recognised, including the ones IANA has since renamed (Kyiv, Greenland, Rangoon), and a label with Canvas's offset pair still attached is accepted. A label that cannot be read is reported, not silently ignored. NoteESTand-05:00deliberately do not resolve: one names half a year, the other says nothing about DST.- With no zone declared at all, the times are taken to be course-local already.
- Times are minute-only. Canvas's time box has no seconds field, so seconds cannot be
written;
11:59 PMis what a person types and Canvas applies its own:59. Seconds you type are accepted and dropped. - Dates and times are read forgivingly, because spreadsheets reformat them:
2026-02-01,2/1/2026,1 Feb 2026,2026年2月1日;23:59,11:59 PM,2359. A numeric date that could be US or European is resolved US-first and reported; one that is real in only one order is read that way silently; one that is real in neither is refused.
What push will not do
- assignments with per-section or per-student overrides — saving Canvas's edit form submits every date card, so a wrong move there would silently delete a student's accommodation date. Untested, so refused outright;
- a time that does not exist, in the hour skipped by a daylight-saving change;
- a date with no time, when the sheet's zone differs from the course's — converting would have to assume a time, and a different assumed time lands on a different date;
- anything Canvas itself will reject. Its rules are enforced server-side and reported
only on the page, so
pushboth checks what it can up front and reads Canvas's answer back afterwards:
4 row(s) have dates Canvas will not accept:
7289053: lock_at 2026-10-04 23:59 is before due_at 2026-10-09 23:59 -- Canvas
refuses this ('Until date cannot be before due date')
Fix these cells in the sheet; they will be skipped.
Ordering is caught before any page is loaded. Other rules — dates before the term start, for instance — surface as Canvas's own wording after the save attempt. Either way the row is skipped and the rest of the push proceeds.
Clearing a date works: leave the cells empty in a column the sheet carries, and push
uses the form's own Clear control.
The infosheet CSV
Everything about an assignment that is not a date:
# canvasser infosheet v1.1,,course=580777,,,,,,,,
Assignment Details,,,,Grading,,Submission,,Availability,,
,needs --rename,read-only,read-only,,,,,false->true only,,read-only
assignment_id,title,kind,assignment_group,points_possible,grading_type,submission_types,allowed_attempts,published,peer_reviews,override_count
7289050,01 - Equipment Demonstration,assignment,Intro Assignments,30,points,external_tool,-1,true,false,0
Row 3 says what push may do with each column. A blank marker means the column is
writable with no ceremony; the others are read-only (a fact rather than a setting),
needs --rename, and false->true only for published, which Canvas offers a control for
in one direction and not the other. It is written for you to read and is never read back —
edit it, delete it, or delete the whole preamble; the header row is found by searching for
assignment_id.
A separate file from the datesheet, and the reason is one rule, not two. A date is the
one field Canvas lets an assignment simply not have — so on the datesheet an empty cell
clears the date. Nothing here can be unset: an assignment always has a publish state,
always sits in exactly one group, and an empty points box is coerced to 0, which would be
a silent grade change rather than an absence. So on the infosheet an empty cell means
"leave this field alone", the same as deleting the column. There is no "clear" marker
because there is nothing for it to mark.
- Rows are matched on
assignment_idalone. There is nooverride_id— these settings are per assignment, not per date card. - Read-only columns:
kind,assignment_group,override_count. They are written so the sheet is legible and ignored on the way back in. (titleis editable — see below — but only with--rename; without it a changed title is reported and skipped.) Two of them open up on aNEWrow:kindandassignment_groupmust be settable when creating, since a new assignment has to be put in a group and the two kinds use different endpoints. Editing never needs either; creating always does. kindexplains the blanks. A classic quiz's page carries nograding_type,submission_typesorpeer_reviewsat all, so those cells are empty on every quiz row — that is a fact about quizzes, not a failed read.kindis one ofassignment,quizordiscussion. A graded discussion is listed among your assignments and is read like any other row, dates and settings included. It cannot be written: Canvas edits those on its discussions app, which canvasser does not drive, sopushnames the row and refuses it — in the preview, before you commit.override_counttells you which assignments carry overrides before you start editing, rather than whenpushrefuses the row.- Values are written exactly as Canvas reports them, including
allowed_attempts = -1for unlimited.
What push writes from it
points_possible, grading_type, submission_types, allowed_attempts,
peer_reviews, and published in one direction. Anything else an editable column can
express is reported per row as NOT WRITABLE YET and skipped — never silently dropped.
Publishing goes one way, and that is Canvas's limit rather than a missing feature. There is no publish checkbox on an assignment form; Canvas offers a second submit button, "Save & Publish", exactly while an assignment is unpublished. So:
| what happens | |
|---|---|
published blank |
left alone |
false -> true |
published, on a NEW row and on an existing one |
true -> false |
reported NOT WRITABLE YET — no control exists. Unpublish it in Canvas |
A row asking to publish says so explicitly in the preview, and the result is read back from Canvas afterwards, because this is the one setting a student notices the moment it changes. If the button is missing when it is needed, the write is refused rather than saved unpublished — asking to publish and quietly not publishing is the failure this refuses.
Each is compared by meaning rather than text, so a spreadsheet's reformatting is not
mistaken for an edit: points numerically (8.34 = 8.340), submission types as a set
(order does not matter), attempts as an integer (3 = 3.0), peer review as a boolean
(TRUE = true, which is what a spreadsheet writes back). Comparing these as strings
would report a change nobody made, write it, and report it again on every push afterwards.
Renaming needs --rename. A changed title is reported and skipped unless you ask
for it:
canvasser push info-580777.csv --rename # preview the rename
canvasser push info-580777.csv --rename --commit # write it
--rename un-gates the one column; it does not write anything on its own, and --commit
remains the only flag that writes. The gate is deliberate: the title is also the column you
read to find your row, so an edit made to keep the sheet legible should not quietly rename
what students see. Nothing is retargeted either way — rows are matched on assignment_id,
so a renamed cell still writes to its own row.
--only narrows a push to the rows you name, which matters because push loads one
page per row and a full sheet can be fifty of them:
canvasser push info-169156.csv --only 3590538
canvasser push info-169156.csv --only 3590538 --only 3591457 --commit
It narrows the sheet before reading, so a one-row check costs one page load instead of fifty. Points to know:
- It matches
assignment_id. On the datesheet that brings a row's override cards with it — they are saved together, so they are selected together. - A narrowed run creates nothing.
--onlynames ids that already exist, so it cannot select aNEWrow, and--only NEWis refused rather than treated as "all the new ones". The run tells you how many new rows it set aside. - An id the sheet does not carry is refused by name, before anything is read or written.
- It is a selector, not a permission: it implies neither
--commitnor--rename.
Length is not pre-checked. Canvas's quiz form declares a 254-character limit and its assignment form declares none, so the limit that applies is read off the form itself: an over-long title is truncated by the box, the read-back no longer matches what was asked for, and the write refuses before saving rather than silently storing a shortened name.
submission_types takes the online sub-types (online_upload, online_text_entry,
online_url, media_recording, student_annotation) or a whole mode (none, on_paper).
external_tool is refused: it needs a tool URL this sheet has no column for, so writing it
would leave an assignment configured for a tool it does not have.
peer_reviews takes true or false — and also yes/no and 1/0, because a
spreadsheet that recognises true as a boolean re-saves it as TRUE. Anything else is
refused before a page is loaded rather than guessed at either way.
allowed_attempts takes a positive count, or -1 for unlimited — Canvas's own
encoding, which is what the sheet carries. Canvas hides the control unless the assignment
accepts submissions, so limiting attempts on a none submission type is refused, naming
submission_types as the thing to set first.
That cuts the other way too, and it is the one case where a write touches a field your
sheet never mentioned. Changing an assignment's submission type to none or on_paper
takes its attempts limit with it — Canvas has nowhere to keep a limit on something nobody
can submit to. The limit is read before the change and reported afterwards, with what
Canvas holds now, so a lost setting is never silent:
AGENT TEST #3590538
submission_types online_text_entry -> none Canvas now: none OK
allowed_attempts Canvas offers no Allowed Attempts control for this submission
type, so its limit of 3 is probably dropped -- the line below
says what it actually did
Canvas now: -1 MISMATCH
A limit that the form drops for any other reason is put back before the save, so an edit to one field cannot quietly unset another.
grading_type takes the option values, not the words on the form: points, percent,
letter_grade, gpa_scale, pass_fail, not_graded. Anything else is refused before a
page is loaded, naming what is accepted:
Check-In 1 #7289061
grading_type='Points' is not one of points, percent, letter_grade, gpa_scale,
pass_fail, not_graded (these are the option values, not the words shown on the form)
Changing points on an assignment that already has graded submissions re-scales every student's percentage, so it is called out against the row and again before writing. It is a warning, not a refusal — the write proceeds.
Changing grading_type to not_graded is called out the same way — beside the row, in
the summary, and again before writing — because it takes the assignment out of grade
calculations and hides its points, and Canvas accepts it without a word:
grading_type points -> not_graded
^ LEAVES THE GRADEBOOK -- 'not_graded' removes this assignment from grade
calculations and hides its points; Canvas will not refuse it
Creating assignments: put NEW in the id cell
Add a row whose assignment_id reads NEW, give it a title and a submission_types,
and push --commit creates it. The id Canvas assigns is written straight back into that
cell, so the next push is an ordinary edit rather than a second assignment. That
write-back is what makes creating safe to re-run — it is the only thing this tool does
that cannot simply be repeated.
assignment_id,title,kind,assignment_group,points_possible,grading_type,submission_types,...
NEW,Homework 1,assignment,Exercises,10,points,online_upload,...
- You must supply
titleandsubmission_types. Canvas defaults the rest —0points, the first assignment group,pointsgrading — and every default you are relying on is named in the preview and again in the result, because a default is only harmless when it is expected.submission_typesis required because Canvas's own default is "Online with nothing ticked", which it then refuses to save: "Please choose at least one submission type." assignment_groupandkindbecome writable on aNEWrow, and only there. Editing an assignment never needs to set them; creating one always does. The group is named the way the sheet shows it — the group's own name, not its id.- Only
assignmentcan be created.kind=quizis refused rather than quietly made an assignment: classic quizzes and New Quizzes are created at different endpoints and the sheet has no column that says which you mean.kind=discussionis refused too — Canvas makes those on a page canvasser does not drive. This matters more than it used to now thatpullwritesdiscussionrows: copying one to make a new row carries thekindcell with it. Make the quiz or discussion in Canvas andpullagain. - Close the file first.
pushrefuses to create anything while your spreadsheet still has the sheet open, and re-reads the file after each write-back to confirm the id landed. If a spreadsheet savedNEWback over a real id, the next push would create a duplicate. publishedworks here too. Set it totrueand the assignment is created already visible to students, using the "Save & Publish" button Canvas offers while an assignment is unpublished. Blank orfalsecreates it unpublished. Either way the preview states which, because that is the one setting a student notices immediately. On an assignment that already exists,false -> trueworks the same way;true -> falsedoes not, because Canvas offers no control for it.- A new assignment has no dates, because dates live in the other sheet. Run
canvasser pullafterwards to pick the new rows up, then edit and push the datesheet.
Nothing is created without --commit. A plain push lists what it would create, with the
count stated plainly — there is no delete command, so an assignment created by mistake
has to be removed in Canvas by hand.
When something fails, it says why
Every failure prints its reason against the line it happened on, rather than a bare count. A post-write mismatch distinguishes Canvas still holds its previous value (the save was rejected — its message is on the edit form) from Canvas holds a third value (it accepted the write and then altered it).
After writing, push re-reads the assignment's own page state and compares date and time,
so a save that silently did not take is reported rather than assumed.
Exit codes
push uses its exit code to say what it found, which matters when you are reading a log
rather than a terminal:
| code | means |
|---|---|
0 |
nothing to do, or the commit finished cleanly |
1 |
a preview with changes pending — not an error |
2 |
one or more items did not land (each reason is printed above), or a field the write had to give up — such as an attempts limit on a submission type that cannot hold one — or the run stopped on an error such as an unreadable sheet, a refused login or a rejected write |
3 |
halted part-way through creating, and the message says what state Canvas is in |
1 is the one to be careful with. A dry run that found work to do exits non-zero by
design, so a script treating any non-zero as failure will call a healthy preview broken.
Data handling
This repository is public.
- Credentials never live in the repo. They belong in the state directory described under "Where it keeps things", which is deliberately outside any working tree.
storage_state.json(the saved session) is credential-equivalent — it grants Canvas access with no password.- The browser profile holds live session cookies and is as sensitive as the password.
- Debug snapshots render real Canvas pages, so they are written outside the repository on purpose — and a page that names students is not captured at all. Gradebooks, rosters, SpeedGrader, submissions and an assignment whose "Assign to" cards target individuals are refused before anything is written; a short text note recording the URL and the reason is left in place of the screenshot, so a failure there is still visible.
- Pulled CSVs are gitignored: they will contain per-student rows once individual overrides are in scope.
License
GPL-3.0-or-later. See LICENSE.
This program is free software: you may redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It comes with absolutely no warranty.
Scope: official Instructure hosts only
Built against UF and extended to other institutions. Point it elsewhere with
--institution <subdomain>, which keeps that Canvas's credentials, saved session and
browser profile in their own directory — so a second account cannot overwrite the first's
session. Selection follows the usual precedence: --institution, then
$CANVAS_INSTITUTION, then default_institution in the secrets file, then the account you
already set up.
Two things another institution must supply, because neither is guessable:
CANVAS_SSO_PATH— the Canvas login route. There is deliberately no default:/login/saml/355is UF's own SAML provider id, and using it elsewhere would send your credentials to UF's identity provider. Unset, a non-UF institution is refused.- A supported second factor. Two are implemented and have been used against a live login: Duo (UF) and Microsoft Authenticator (UCF), each with a push to approve or a code to type. Temple asks for none, and that works too — whether a second factor is presented at all is the provider's decision. A third would be an addition rather than a rewrite: sign-in is driven by recognising what is on screen, not by following a fixed sequence of steps.
Only *.instructure.com hosts are supported. Self-hosted and vanity-domain Canvas are
refused outright: the institution is identified by its subdomain, and a host without one
would make two different schools indistinguishable in the state directory.
Release files for canvasser 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| canvasser-0.2.0.tar.gz | 205.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| canvasser-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 398.6 kB
Release files / canvasser-0.2.0.tar.gz
| Download URL | canvasser-0.2.0.tar.gz |
|---|---|
| Size | 205.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5a98fb23389fcf9546665e01599d0f76098ad6d8222b1bbba579d67f902e22ed
|
|
BLAKE2b-256 checksum How to use checksums |
edab10657e6f67a2eaa16eb16c02bd75ad93f0019664d12c6b459e4ae5d13d23
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|
Release files / canvasser-0.2.0-py3-none-any.whl
| Download URL | canvasser-0.2.0-py3-none-any.whl |
|---|---|
| Size | 192.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
105382312717db11221ec79c86e2c8189f50b204a367663bcaed1909fbdafd98
|
|
BLAKE2b-256 checksum How to use checksums |
0133e463f945fd2827fa6036e5af67982e0f2ca436de0b6fe93e1eaef98d793d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|