# CanvasAPI wraps the Canvas LMS API in Python objects

> CanvasAPI is a Python client for the Instructure Canvas LMS, maintained by the University of Central Florida's Center for Distributed Learning. It gives scripts typed objects for courses, users and paginated collections, at the cost of an API surface that tracks Canvas itself rather than your own domain.

**ucfopen/canvasapi** — Python API wrapper for Instructure's Canvas LMS. Easily manage courses, users, gradebooks, and more.

- Repository: https://github.com/ucfopen/canvasapi
- Website: https://pypi.python.org/pypi/canvasapi
- Stars: 681 · Forks: 192
- Language: Python
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/ucfopen-canvasapi

## One entry class and a tree of objects behind it

CanvasAPI is a Python library for the Instructure Canvas LMS API, and its shape is simple: a single class named Canvas gives access to everything else. You instantiate it with your instance root URL and an API key, and from that object you pull courses, users, assignments and the rest as Python objects.

The design decision that makes the library pleasant is that JSON responses are converted into Python objects that themselves expose further API access. The README describes it as converting the JSON responses from the Canvas API into Python objects, and those objects provide further access to the API. In practice that means a course object is not a plain dict you have to post back, and a user object can ask for its own enrolled courses without you reassembling a URL.

The audience is narrower than the wording suggests. The project is authored by the University of Central Florida's Center for Distributed Learning, and the package classifiers name developers, education and information technology as intended audiences. It is an institutional teaching tool with an academic origin, and the ecosystem section of the README lists things like Canvas Grab for copying course files locally and Clanvas, a command line client, alongside a Discord bot built for a specific university course.

One thing the README does not describe is any authentication scheme beyond the API key, so if your tenant uses OAuth or an external tool access token, the mapping onto this client is not documented here.

## Installing the package and making the first calls

Installation is a single pip command, pip install canvasapi, as the README states. The package brings three runtime dependencies, which setup.py declares like this:

```python
install_requires=["arrow", "pytz", "requests"],
```

The quickstart then asks you to build a Canvas object with a URL and a key:

```python
# Import the Canvas class
from canvasapi import Canvas

# Canvas API URL
API_URL = "https://example.com"
# Canvas API key
API_KEY = "p@$$w0rd"

# Initialize a new Canvas object
canvas = Canvas(API_URL, API_KEY)
```

Note what this snippet does and does not teach. It shows the constructor signature, but it does not show how the key is meant to be stored, and it does not mention a base URL check or a test call to verify the credentials. If you are pasting this into a script, treat the key as a secret you keep in your environment, because the library itself will send whatever string you hand it.

Once you have the object, fetching and changing a course takes two calls:

```python
# Grab course 123456
>>> course = canvas.get_course(123456)

# Access the course's name
>>> course.name
'Test Course'

# Update the course's name
>>> course.update(course={'name': 'New Course Name'})
```

That last line is the pattern worth noticing. Updates take a dictionary keyed by the Canvas field name, nested under the field the endpoint expects, which is why the README points at its keyword arguments documentation for the details of how the name argument is handled. Users work the same way, and can be looked up by identifier or by SIS login id:

```python
# Grab user 123
>>> user = canvas.get_user(123)

# Access the user's name
>>> user.name
'Test User'

# Retrieve a list of courses the user is enrolled in
>>> courses = user.get_courses()

# Grab a different user by their SIS ID
>>> login_id_user = canvas.get_user('some_user', 'sis_login_id')
```

## PaginatedList loads lazily and cannot tell you how long it is

Any call that returns many objects, such as the enrolled courses call above, gives you a PaginatedList rather than a plain list. It behaves like a list for indexing, iteration and slicing, and the README shows all three:

```python
# Retrieve a list of courses the user is enrolled in
>>> courses = user.get_courses()

>>> print(courses)
<PaginatedList of type Course>

# Access the first element in our list.
#
# You'll notice the first call takes a moment, but the next N-1
# elements (where N = the per_page argument supplied; the default is 10)
# will be instantly accessible.
>>> print(courses[0])
TST101 Test Course (1234567)
```

The warning in the documentation is the part that will bite you in production. PaginatedList loads its elements lazily, and there is no way to know the exact number of records Canvas will return without traversing the list fully. Two consequences follow. The object is unaware of its own length, so len() on it will not give you a record count, and negative indexing is not supported.

That is not a small edge case. Any report that wants to print a total, or that preallocates a buffer, or that takes the last page of results, has to walk the whole collection first and count as it goes. For a course with a few hundred enrollments that is fine. For a term-wide pull across a large institution it is a real cost, and it is the practical limit of this library for bulk extraction.

## Keyword arguments pass Canvas endpoint parameters straight through

Most Canvas endpoints accept a range of arguments, and CanvasAPI exposes them as Python keyword arguments rather than forcing you to hand build query strings:

```python
# Get all of the active courses a user is currently enrolled in
>>> courses = user.get_courses(enrollment_state='active')
```

The mechanism is a thin translation layer, which is both the strength and the risk. The strength is that the call reads like the Canvas documentation, so someone who knows the API can write the script without learning a wrapper vocabulary. The risk is that the argument names and semantics belong to Canvas, not to CanvasAPI, so the library can only follow the upstream API and cannot stabilise it for you.

More complex arguments are where a wrapper earns its keep. The README points at a separate Keyword Argument Documentation page for the details of how the library handles nested or structured arguments, and the course update example above shows the pattern of nesting values under the field name the endpoint expects. If you are building nested payloads for an assignment or a rubric, that page is the reference to read before you guess.

The documentation set lives on Read the Docs, with a class reference page that lists the methods each object provides. The README also notes that full documentation is published at canvasapi.readthedocs.io, with the stable version behind that host.

## A small dependency footprint, MIT licensed, no Python 2

The packaging is deliberately plain. setup.py pulls the version out of canvasapi/__init__.py with a regular expression, reads the README for the long description, and declares the install requirements as arrow, pytz and requests. That is the whole runtime footprint: a date and time library, a timezone library and an HTTP client. There is no ORM, no framework and no async runtime in the list.

The license is MIT, and the setup metadata says so twice, in the license field and in the OSI classifier. That is a permissive grant with no fee and no per-user obligation, though it is worth reading the LICENSE file yourself if your institution needs the exact text.

The supported Python versions are declared in the classifiers: 3.10, 3.11, 3.12, 3.13 and 3.14. The package is marked Operating System :: OS Independent and Development Status :: 5 - Production/Stable. The repository layout matches a well-organised library, with the canvasapi package, docs, tests, separate requirements files for runtime, development and tests, a CHANGELOG.md, a DEPLOY.md, and a .github directory holding the contributing guide.

Two details from setup.py are worth knowing if you vendor this. The package list is a single entry, canvasapi, and the distribution is not marked zip_safe, which tells you a source checkout is expected rather than an egg installed from a zipped egg.

## Release gaps, and a last push in April 2026

The release history shows uneven cadence. Version 3.5.0 was published on 2026-03-12, 3.4.0 on 2025-12-16 with a tag date of 2025-11-10, and 3.3.0 on 2024-08-27 with a tag date of 2023-08-27. Between 3.3.0 and 3.4.0 there is a gap of more than a year, so this is a project that ships when there is something to ship rather than on a schedule.

The last push to the repository was on 2026-04-16. The default branch is develop, and the build badge in the README points at that branch, so the working branch is where you would look for in-progress changes. The releases on PyPI are what you install, and the version you get there is the version that was tested at release time.

That gap matters for anyone planning to depend on this library in a scheduled job. If a Canvas tenant upgrade changes an endpoint's behaviour, there is no guarantee a new release follows quickly. The mitigation is not complicated: pin the version, and if you depend on a specific endpoint behaviour, keep a small regression script of your own that exercises it against your tenant.

The project also points at a UCF Open Slack for discussions, which is where questions about usage and contribution are likely to be answered.

## The alternative is requests, and the difference is object shape

You can talk to Canvas without this library. The API is HTTP and JSON, and the requests package is already a dependency of CanvasAPI, so calling endpoints directly is entirely feasible for a script that needs a handful of records.

The difference in approach is object shape and error handling. Raw requests give you dicts and you write the field access, the pagination loop and the update payload yourself. CanvasAPI gives you a course object with a name attribute, a user object that can fetch its own courses, and a PaginatedList that behaves like a list, which removes a category of code where the interesting logic is buried in dictionary access.

That convenience has a price, and it is worth being clear about where the line falls. CanvasAPI is a thin layer, so it cannot give you an abstraction above Canvas. If your real problem is enrolling students from your SIS into a course, no wrapper saves you from the API's own semantics, and you will still write request shaping in whatever form the library requires. The library helps with the plumbing, not with the domain.

The other alternative worth naming is the command line client in the project's own ecosystem list, Clanvas, which reaches the same API from a shell. For an interactive operator who wants to check one course, that is lighter than a Python import. For anything scripted, repeatable and scheduled, a Python object model is the better fit.

## Conclusion

CanvasAPI suits administrators and researchers who script against Canvas and want Python objects instead of raw JSON, especially for read heavy reporting over enrollments, gradebooks and course content. It does not suit teams that need versioned endpoints, offline access to large paginated exports, or a client that ships a stable contract of its own, because the library mirrors Canvas endpoints directly and its PaginatedList cannot report its own length. Before adopting it, confirm that your Canvas tenant exposes the API key you plan to use, pin the version you tested against since the last release was 3.5.0 on 2026-03-12, and read the paginated list behaviour before you build a report that assumes a known record count.

## FAQ

### How do I install CanvasAPI?

The README gives a single pip command, `pip install canvasapi`. The runtime dependencies are arrow, pytz and requests, and the package classifiers list Python 3.10 through 3.14.

### How do I create a CanvasAPI client and fetch a course?

Instantiate the Canvas class with your instance root URL and API key, for example `canvas = Canvas(API_URL, API_KEY)`, then call `canvas.get_course(123456)` to get a course object with attributes such as name. Updating a course uses `course.update(course={'name': 'New Course Name'})`.

### Why does len() fail on a PaginatedList from CanvasAPI?

PaginatedList loads its elements lazily and there is no way to know how many records Canvas will return without traversing the list fully, so the object is not aware of its own length. Negative indexing is also not supported.

### What license is CanvasAPI released under?

The project is MIT licensed, and the setup metadata records it as MIT License with the matching OSI classifier. The LICENSE file in the repository holds the full text.

## Sources

- [License: MIT](https://github.com/ucfopen/canvasapi/blob/develop/LICENSE)
- [Project website](https://pypi.python.org/pypi/canvasapi)
- [README](https://github.com/ucfopen/canvasapi/blob/develop/README.md)
- [Releases](https://github.com/ucfopen/canvasapi/releases)
- [ucfopen/canvasapi on GitHub](https://github.com/ucfopen/canvasapi)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ucfopen-canvasapi
