Skip to content

Course Units API

Francisca Portugal edited this page Oct 27, 2026 · 1 revision

Course units API

Abstract course units and their concrete occurrences. Status: current. Part of the API Catalog.

Both routes require a session; anonymous callers get 401 unauthorized.

Status Method Path Description
✅ GET /v1/course-units/:id One abstract course unit
✅ GET /v1/course-units/:id/occurrences Every occurrence of the unit, newest year first

CourseUnit vs Occurrence

This is the distinction that causes the most confusion, so state it once:

Concept What it is Example
Course unit Year-agnostic template of a subject BD — Bases de Dados (id 42)
Occurrence The unit actually taught, in one course, in one year BD in L.EIC, 2025 (id 9001)

A course unit owns no classes, periods or enrolments. Those belong to the occurrence. The same unit taught in two courses or two years has one CourseUnit row and two Occurrence rows.

GET /v1/course-units/:id

GET /v1/course-units/42
→ 200 CourseUnit
Status code When
200 — Found
401 unauthorized No session
404 course-unit-not-found The id does not exist
422 validation-error :id is not an integer

CourseUnit

{ "id": 42, "courseId": 22841, "acronym": "BD", "name": "Bases de Dados", "semester": 1 }
Field Type Notes
id number Unit id
courseId number The course this unit row belongs to
acronym string e.g. BD
name string Full name
semester 1 | 2 Narrowed at the repository boundary

The same subject in another course or year is a different CourseUnit row with its own id.

GET /v1/course-units/:id/occurrences

GET /v1/course-units/42/occurrences
→ 200 [Occurrence]
  • The unit is checked first: an unknown id is 404 course-unit-not-found.
  • A unit that has never run returns [].
  • Ordered by year descending, then id ascending — newest teaching instance first.

Occurrence

{
  "id": 9001,
  "courseUnitId": 42,
  "courseId": 22841,
  "year": 2025,
  "ects": 6,
  "url": "https://sigarra.up.pt/feup/pt/ucurr_geral.ficha_uc_view?pv_ocorrencia_id=9001",
  "hash": "…"
}
Field Type Notes
id number SIGARRA occurrence id, unique per academic year
courseUnitId number The abstract unit
courseId number The course it ran in
year number Academic year
ects number ECTS credits for that occurrence
url string SIGARRA occurrence page
hash string Content hash used to detect catalog changes

ects lives on the occurrence, not the unit: the same unit can carry different credits in different courses.

Not exposed yet

Classes, lesson types, teachers and schedule slots hang off an occurrence and are not implemented:

CourseUnit ──< Occurrence ──< Class ──< Slot
                                │
                                └── lesson type, teachers, vacancies

endpoints.md designs GET /courses/:id/occurrences, GET /occurrences/:id/classes and GET /occurrences/hashes. None are registered. Today the only way to reach an occurrence is through this page's second route. The Occurrences page is 🚧.

Implementation

Concern File
Routes apps/backend/src/interface/routes/course-units.routes.ts
Response schemas apps/backend/src/interface/routes/catalog.schemas.ts
Ports domain/ports/course-unit.repository.ts, domain/ports/occurrence.repository.ts
Prisma repositories infrastructure/database/repositories/prisma-course-unit.repository.ts, prisma-occurrence.repository.ts
Tests apps/backend/tests/interface/routes/catalog/course-units.test.ts

The repository narrows the semester int column to 1 | 2 at the boundary, so a corrupt row throws there instead of producing a response that fails its own schema.

See also

  • Courses API — the course → units edge
  • Glossary — unit vs occurrence vs class
  • API Catalog — conventions and error contract
  • Occurrences 🚧 · Domain model 🚧

Last verified: 2026-10-27

Clone this wiki locally