Repository navigation
Courses API
Read-only course catalog and the course → course-unit edge. Status: current. Part of the API Catalog.
All three routes require a session; anonymous callers get 401 unauthorized.
| Status | Method | Path | Description |
|---|---|---|---|
| ✅ | GET |
/v1/courses |
All courses; optional ?year= and ?faculty=
|
| ✅ | GET |
/v1/courses/:id |
One course |
| ✅ | GET |
/v1/courses/:id/units |
Course units offered by a course |
A course is a degree programme in a specific academic year (L.EIC 2025, MEIC 2025). A course
exists per year, so the same programme has a different id in a different year.
GET /v1/courses?year=2025&faculty=FEUP
→ 200 [Course]| Query | Required | Notes |
|---|---|---|
year |
no | Academic year as an integer |
faculty |
no | Faculty acronym, matched against the offering faculties |
- Both filters are optional and combine (
AND). - Ordered by
acronymascending. - An unknown
facultymatches nothing and returns[]— not a404. - A year with no courses returns
[].
This is looser than GET /v1/faculties/:acronym/courses,
which checks the faculty exists first.
| Status | code |
When |
|---|---|---|
200 |
— | Matches, possibly []
|
401 |
unauthorized |
No session |
422 |
validation-error |
?year=abc |
GET /v1/courses/22841
→ 200 Course| Status | code |
When |
|---|---|---|
200 |
— | Found |
401 |
unauthorized |
No session |
404 |
course-not-found |
The id does not exist |
422 |
validation-error |
:id is not an integer |
GET /v1/courses/22841/units
→ 200 [CourseUnit]- The course is checked first: an unknown id is
404 course-not-found. - A course that exists but offers no units returns
[]. - Ordered by unit acronym.
CourseUnit is the abstract, year-agnostic template. The concrete teaching instance — with
ECTS, classes and schedule slots — is an occurrence; see
Course units API.
{
"id": 22841,
"acronym": "MEIC",
"name": "Mestrado em Engenharia Informática e Computação",
"courseType": "M",
"year": 2025,
"url": "https://sigarra.up.pt/feup/pt/cur_geral.cur_view?pv_curso_id=22841",
"faculties": ["FEUP"]
}| Field | Type | Notes |
|---|---|---|
id |
number | SIGARRA course id; already per academic year |
acronym |
string | e.g. L.EIC
|
name |
string | Full name |
courseType |
string | Raw SIGARRA code (L, M, P, …) |
year |
number | Academic year, e.g. 2025
|
url |
string | SIGARRA course page |
faculties |
string[] | All faculties that offer it |
faculties is an array because courses can be shared (["FCUP", "FEUP"]). It is never null;
a course with no join row gets [].
courseTypeis not a reliable "degree type". The raw SIGARRA code is inconsistent across courses and is being investigated in #891. Do not branch on it for planner logic. See the glossary for the naming traps.
| Concern | File |
|---|---|
| Routes | apps/backend/src/interface/routes/courses.routes.ts |
| Response schemas | apps/backend/src/interface/routes/catalog.schemas.ts |
| Ports |
domain/ports/course.repository.ts, domain/ports/course-unit.repository.ts
|
| Prisma repositories |
infrastructure/database/repositories/prisma-course.repository.ts, prisma-course-unit.repository.ts
|
| Shared course read / faculty join | infrastructure/database/repositories/course-mapper.ts |
| Tests | apps/backend/tests/interface/routes/catalog/courses.test.ts |
Prisma has no implicit many-to-many in Prisma 8, so the offering faculties come from an explicit
FacultyCourse join table (one extra query, grouped into courseId → acronyms).
- Faculties API — courses scoped to a faculty
- Course units API — units and occurrences
- Glossary — course vs course unit vs occurrence
- API Catalog — conventions and error contract
Last verified: 2026-10-27