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.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.
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);
}
// 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).
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.
- 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
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.
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.
- MCP server exposing the client as tools → ntnu-mcp
- Expose the remaining catalog search facets (campus, level, faculty/institute)
- 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