utoronto APIutoronto.ca ↗
Access U of T academic calendars, course timetables, and researcher profiles via 9 endpoints covering St. George, Mississauga, and Scarborough campuses.
What is the utoronto API?
The University of Toronto API exposes 9 endpoints covering academic calendar data across three campuses, current course timetables, and the Discover Research faculty directory. With search_courses you can query courses by keyword or subject area and get back prerequisites, corequisites, exclusion rules, breadth requirements, and distribution tags. The same API surfaces program listings, section-level timetable data, and full researcher profiles including degrees, publications, and ORCID identifiers.
curl -X GET 'https://api.parse.bot/scraper/d8196962-a57c-4b65-9e08-70dc525a9712/list_subject_areas?campus=st_george' \ -H 'X-API-Key: $PARSE_API_KEY'
Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace utoronto-ca-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""Walkthrough: University of Toronto academic data — courses, experts, publications."""
from parse_apis.utoronto_ca_api import UofT, Campus, Division, InputNotFound
client = UofT()
# Search for computer science courses on the St. George campus.
for course in client.courses.search(campus=Campus.ST_GEORGE, keyword="CSC", limit=5):
print(course.course_code, course.title, course.hours)
# Drill into full detail for the first hit.
hit = client.courses.search(campus=Campus.ST_GEORGE, keyword="CSC108", limit=1).first()
if hit is not None:
detail = client.courses.get(course_code=hit.course_code)
print(detail.title, detail.prerequisite, detail.breadth_requirement)
# Search experts in a department, then navigate to full profile and publications.
expert_summary = client.expert_summaries.search(
department="Faculty of Arts and Science, Department of Computer Science",
limit=1,
).first()
if expert_summary is not None:
expert = expert_summary.details()
print(expert.name, expert.title, expert.email)
# Walk the expert's publications via the sub-resource.
for pub in expert.publications.list(limit=3):
print(pub.year, pub.title, pub.journal)
# Search current timetable offerings for Arts & Science.
for offering in client.course_offerings.search(
division=Division.ARTSC, keyword="CSC108", limit=3
):
for section in offering.sections:
print(offering.course_code, section.section, section.current_enrolment, "/", section.max_enrolment)
# Point-lookup an expert by id — handle not-found gracefully.
try:
expert = client.experts.get(expert_id="999999999")
except InputNotFound:
print("expert not found")
print("exercised: courses.search / courses.get / expert_summaries.search / details / publications.list / course_offerings.search / experts.get")
Lists the calendar subject areas (program areas / calendar sections, i.e. the departments and units the calendar is organised by) for one campus calendar, read from the calendar's course search filter. Returns one row per subject area with the exact value accepted by search_courses.subject_area and search_programs.subject_area, plus its calendar section page URL. For the Mississauga campus the calendar also exposes an academic department list, returned in departments (empty for other campuses). One request; not paginated.
| Param | Type | Description |
|---|---|---|
| campus | string | Which campus calendar to read. |
{
"type": "object",
"fields": {
"campus": "campus key echoed back",
"campus_name": "human-readable campus name",
"departments": "array of department names (Mississauga calendar only, else empty)",
"calendar_url": "base URL of the campus academic calendar",
"subject_areas": "array of {subject_area (filter value), label, source_url}"
},
"sample": {
"data": {
"campus": "utm",
"campus_name": "Mississauga",
"departments": [
"Anthropology",
"Biology"
],
"calendar_url": "https://utm.calendar.utoronto.ca",
"subject_areas": [
{
"label": "Anthropology",
"source_url": "https://utm.calendar.utoronto.ca/section/Anthropology",
"subject_area": "Anthropology"
}
]
},
"status": "success"
}
}About the utoronto API
Academic Calendar Coverage
The list_subject_areas endpoint returns every subject area filter value recognised by a given campus calendar — St. George (Arts & Science), Mississauga, or Scarborough. Those subject_area values feed directly into search_courses and search_programs. search_courses returns up to 30 courses per page with fields including course_code, title, description, hours, prerequisite, corequisite, exclusion, recommended_preparation, and breadth/distribution requirement tags. get_course resolves a single course code to its full calendar entry; the campus is inferred automatically from the code's final digit (1 = St. George, 5 = Mississauga, 3 = Scarborough). The Mississauga calendar additionally accepts a department filter on both course and program searches.
Programs of Study
search_programs searches all program types — specialists, majors, minors, focuses, certificates, and combined degrees — and returns program_code, name, level, subject_area, description, and enrolment and completion requirement text. The level input is matched case-insensitively by prefix against the calendar's own level labels, so passing Specialist or Minor narrows results without needing an exact string. total_pages and has_more on both course and program responses let you page through large result sets reliably.
Timetable Builder
list_timetable_divisions returns the currently searchable academic sessions (sessions[*].code and sessions[*].label) and all faculty/division codes. Feed a division code into search_course_offerings to retrieve current offerings: each row carries course_code, section_code, term indicator, campus, instructor names, department, faculty, and the linked session labels. You can narrow by keyword, session, department, and instructor surname. Results page at 20 offerings per page with a total count in the response.
Researcher Profiles and Publications
search_experts queries the Discover Research directory by free-text query or by exact faculty_and_department label, returning up to 100 profiles per page with expert_id, name, title, positions, and faculty_and_department. get_expert expands a single profile to include bio, email, orcid, degrees (with field of study, institution, and year), websites, languages, and classification labels. list_expert_publications pages through an expert's linked works with title, type, year, journal, doi, issn, authors, and co-author expert_id references for graph traversal.
The utoronto API is a managed, monitored endpoint for utoronto.ca — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when utoronto.ca changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official utoronto.ca API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Build a course prerequisite checker using
get_courseprerequisite and corequisite fields for a given course code. - Aggregate current instructor assignments across a division using
search_course_offeringsfiltered by instructor surname. - Map program requirements for all Computer Science majors and specialists at St. George using
search_programswith subject_area. - Construct a faculty research-interest graph by combining
search_expertskeyword results withget_expertclassification labels and positions. - Export a researcher's full publication list with DOIs and co-author links using
list_expert_publicationspaged responses. - Compare course offerings across sessions for a single department by iterating
search_course_offeringsover each session code fromlist_timetable_divisions. - Identify all subject areas available on the Scarborough calendar for a course catalog mirror using
list_subject_areaswith the Scarborough campus key.
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 200 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
| Team | $300/mo | 20,000 | 300 req/min |
| Company | $1,000/mo | 100,000 | 500 req/min |
Each endpoint has a fixed posted price per successful call — most fall between 1 and 10 credits — shown on this API's page before you run it. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.
Does the University of Toronto have an official public developer API?+
How does `get_course` know which campus calendar to use?+
search_courses or search_course_offerings.Does the `department` filter work on all three campuses in `search_courses`?+
department parameter is honoured only by the Mississauga campus calendar; it is ignored for St. George and Scarborough queries. For those two campuses, use subject_area values from list_subject_areas to narrow results.Does the API cover graduate (SGS) course calendars or graduate programs?+
Can I retrieve historical timetable data or past academic sessions?+
list_timetable_divisions. Historical sessions are not surfaced. You can fork this API on Parse and revise it to archive session data over time if longitudinal records are needed.