ntnu-api
v0.4.1
Published
TypeScript client for NTNU course data APIs (courses, grades, semesters)
Readme
ntnu-api
TypeScript client for NTNU course data. Wraps the three public, no-auth data sources for course information at the Norwegian University of Science and Technology behind one typed interface. Fetch-based with zero runtime dependencies — works in Node ≥ 20, Cloudflare Workers, browsers, and any other fetch-capable runtime.
| Namespace | Data | Backing source |
| --- | --- | --- |
| client.courses | catalog search, per-course schedules & weekly timetables | ntnu.no course-page JSON (Liferay) |
| client.courses.details | exam info (date, time, duration, aid code, rooms), fact box, descriptions, study programs | ntnu.no course-page HTML (scraped — no JSON upstream) |
| client.grades | grade distributions per course/year/semester | HK-dir DBH statistics API |
| client.programs | study-program catalog (~400 programs), per-cohort study plans (courses per semester, specializations) | ntnu.no program pages (Liferay JSON) |
| client.semesters | term ids (26h/26v), teaching weeks, exam periods | NTNU's TP timetable system |
See docs/api-research.md for the full survey of NTNU data sources (including the dead and auth-gated ones) and the exact upstream request shapes.
Usage
npm install ntnu-apiimport { NTNUClient } from "ntnu-api";
const client = new NTNUClient();
// Server-side course search (one 500-course page)
const page = await client.courses.search(2026, "objektorientert");
for (const hit of page.courses.slice(0, 3)) {
console.log(hit.courseCode, hit.courseName);
}
// Full catalog iteration (dedups the upstream's duplicate entries)
for await (const resultPage of client.courses.searchAll(2026)) {
// ...
}
// Dated teaching activities and the summarized weekly grid
const activities = await client.courses.schedules("TDT4100", 2026);
const grid = await client.courses.timetable("TDT4100", 2026);
// Everything else on the course page (HTML-only upstream, scraped):
// exams with start time/duration/hjelpemiddelkode/rooms, credits, level,
// descriptions, and the study programs the teaching is planned for —
// details.studyPrograms maps the studyProgramKeys on schedule/timetable
// entries to program names.
const details = await client.courses.details("TDT4100", 2026);
for (const exam of details?.exams ?? []) {
console.log(exam.occasion, exam.date, exam.time, exam.duration, exam.aidCode);
}
// Study programs: the catalog, and the full plan for a cohort
// (course codes+versions per semester, specialization choice points).
// planCourseCodes() flattens a plan to its course codes.
const programs = await client.programs.all();
const plan = await client.programs.studyPlan("MTDT", 2024);
// Grade distribution (accepts bare or DBH-versioned codes)
for (const row of await client.grades.distribution("TDT4100", { years: [2023] })) {
console.log(row.year, row.semesterName, row.grade, row.total);
}
// Terms as known by NTNU's timetable system
const current = await client.semesters.current();All errors derive from NTNUAPIError (with NotFoundError,
RateLimitError, APITimeoutError, ... subclasses). Transient failures
(429, 502–504, connection errors) are retried with exponential backoff,
honoring Retry-After. Upstream payloads are parsed into plain typed
objects; helpers like bestName() (trilingual name fallback) and
weekNumbers() (ISO-week range expansion) are exported alongside them.
A word of caution: the ntnu.no endpoints are NTNU's own internal AJAX surface, not a documented API — treat results accordingly and be polite (the client ships a descriptive User-Agent by default).
MCP server
The ntnu-mcp sibling repo hosts a
remote MCP server built on this package (Cloudflare Worker), exposing the
client as ten LLM-shaped tools — connect Claude or ChatGPT to
https://ntnu-mcp.martinsundal.no/mcp. Upstream fixes and new data sources
land here; the MCP repo only shapes typed data for LLMs.
Stack
- TypeScript (strict), ESM-only, zero runtime dependencies
- vitest — tests over captured live fixtures
- Biome — linting + formatting
- mise — pins Node and exposes task shortcuts
- Dev Container — reproducible environment
Development
mise install # pinned Node
npm install| Task | Command |
| ---------------- | ------------------- |
| mise run test | vitest run |
| mise run lint | biome check . |
| mise run fmt | biome check --write . |
| mise run typecheck | tsc --noEmit |
| mise run build | tsc -p tsconfig.build.json → dist/ |
| mise run check | lint + typecheck + test |
The default test run is fully mocked (injected fetch) against real
captured payloads in tests/fixtures/. To also verify against the live
endpoints (drift check):
NTNU_API_LIVE=1 npm testReleases are tag-driven: npm version <patch|minor|major> && git push
--follow-tags. The release workflow then
verifies the tag, runs the full check suite, publishes to npm (trusted
publishing with provenance — no token secrets), and creates the GitHub
Release with generated notes. The prepare script builds dist/, so
github:MartinSA04/ntnu-api installs also work between releases.
History
This library began as an async Python client (httpx + pydantic, with a CLI
and a stdio MCP server); it was ported 1:1 to TypeScript so one codebase
serves Node, Workers, and browsers, per
docs/ts-migration-spec.md. The final Python
state is preserved at the
python-final
tag.
Roadmap
- [x] MCP server exposing the client as tools → ntnu-mcp
- [ ] Expose the remaining catalog search facets (campus, level, faculty/institute)
- [x] Course detail scraping (exams, description, credits, prerequisites — HTML-only upstream) →
courses.details() - [ ] TP
emner.phpsemester catalog (English names, campus ids — see docs/api-research.md) - [ ] TP iCal export as a secondary timetable source once semesters publish
