SchoolSoft+ LogoSchoolSoft+DEVELOPER
On this page
NPM LIBRARY
ssp-node logo

SchoolSoft+ Node

GitHub

@elias4044/ssp-nodeis the official Node.js library for the SchoolSoft+ platform. It wraps SchoolSoft's unofficial internal REST API and gives you a clean, typed interface to authenticate, fetch schedules, assignments, lunch menus, messages, grades, attendance, and more — all without any runtime dependencies beyond Node's built-in modules.

This library targets SchoolSoft's unofficial, reverse-engineered API. Endpoints may change without notice.

Introduction

SchoolSoft+ Node provides two ways to interact with SchoolSoft:

  • SchoolsoftClient class — A stateful client that holds your session, automatically refreshes tokens, and exposes every endpoint as a method. This is the recommended approach for most use-cases.
  • Functional API — Every internal function used by the class is also exported individually. Useful for serverless environments, functional codebases, or when you want fine-grained control.

The library uses Node's built-in https module for all HTTP requests — no axios, no node-fetch, no runtime dependencies whatsoever. It is fully typed with TypeScript and ships its own type declarations.

Installation

bash
npm install @elias4044/ssp-node

Requires Node.js 18+ (uses the crypto and https built-ins). Works in CommonJS and ESM projects. TypeScript 5+ is recommended.

Quick start

The fastest path from zero to fetching data is a simple username/password login:

typescript
import { SchoolsoftClient } from '@elias4044/ssp-node';

const client = new SchoolsoftClient({ school: 'engelska' });

await client.login({ username: 'john.doe', password: 'mypassword' });

const lunch    = await client.getLunch(22);         // week 22
const schedule = await client.getSchedule();        // current week (auto-detected)
const news     = await client.getNews();

For longer-lived sessions (e.g. a mobile app or a daemon), use the mobile OAuth2 flow to get a refresh token:

typescript
const client = new SchoolsoftClient({ school: 'engelska' });

await client.mobileLogin({ username: 'john.doe', password: 'mypassword' });
await client.mobileExchangeSession();   // converts access token → session cookies

const subjects = await client.getSubjects();

Authentication — Simple login

Simple login submits your credentials to SchoolSoft's JSP login form. On success, SchoolSoft returns an HTTP 302 redirect and sets three cookies:JSESSIONID, hash, andusertype. The library captures these automatically.

typescript
const result = await client.login({
  username: 'john.doe',
  password:  'secret',
  usertype:  '1',      // '1' = student (default), '2' = guardian, '3' = staff
});

console.log(result.jsessionid);   // raw JSESSIONID cookie value
console.log(result.hash);         // raw hash cookie value
console.log(result.cookieHeader); // 'JSESSIONID=...; hash=...; usertype=1'

login() — options

NameTypeRequiredDescription
usernamestringYesYour SchoolSoft username (typically firstname.lastname).
passwordstringYesYour SchoolSoft password.
usertypestringNo'1' = student (default), '2' = guardian, '3' = staff.

Returns SimpleLoginResult.

Simple login sessions are tied to a browser-style JSESSIONID. They may expire after a period of inactivity (typically a few hours). Use verifySession() to check liveness, or prefer the mobile flow for longer-lived sessions.

#Restoring a saved simple-login session

You can persist the cookie values after login and restore them on the next startup without re-authenticating:

typescript
// Save after login
const { jsessionid, hash, usertype } = await client.login({ ... });
// e.g. store in a file, database, or environment variable

// Restore on next startup
client.setSessionCookies(jsessionid, hash, usertype);

// Optionally verify before using
const alive = await client.verifySession();
if (!alive) await client.login({ ... }); // re-authenticate if expired

Authentication — Mobile login (automated)

The mobile flow mirrors how the official SchoolSoft eApp authenticates — using OAuth2 + PKCE. It gives you a short-lived access token (≈ 15 min) and a long-lived refresh token. You then exchange the access token for session cookies to use the REST API.

typescript
const client = new SchoolsoftClient({ school: 'engelska' });

// Step 1 — obtain access + refresh tokens
const { accessToken, refreshToken, expiresAt } = await client.mobileLogin({
  username: 'john.doe',
  password:  'secret',
  orgid:     '18',    // school organisation ID, defaults to '18'
});

// Step 2 — (optional) fetch user info to get the userId for a more reliable exchange
const info = await client.fetchMobileSessionInfo();
// info: { username, firstName, lastName, email, schoolName, userType, userId }

// Step 3 — exchange the access token for session cookies
await client.mobileExchangeSession(info?.userId, {
  orgid:    '18',
  language: 'sw',       // 'sw' (Swedish) or 'en'
  theme:    'dark',
  useros:   'android',  // 'android' or 'ios'
});

// Now you can call any data endpoint
const schedule = await client.getSchedule();

mobileLogin() — options

NameTypeRequiredDescription
usernamestringYesSchoolSoft username.
passwordstringYesSchoolSoft password.
orgidstringNoSchool organisation ID. Defaults to '18'.

mobileExchangeSession() — options

NameTypeRequiredDescription
userIdstring | numberNoThe user's numeric ID. Pass fetchMobileSessionInfo().userId for a more reliable exchange.
orgidstringNoSchool org ID. Defaults to '18'.
languagestringNo'sw' (Swedish, default) or 'en'.
themestringNo'dark' (default) or 'light'.
userosstringNo'android' (default) or 'ios'.
redirectUrlstringNoOverride the post-login redirect URL. Defaults to the subject rooms page.

Authentication — Mobile login (browser / WebView)

For apps that show a login page in a browser or WebView, use the two-step PKCE browser flow. You generate a URL the user opens, then complete the flow after they authenticate and SchoolSoft redirects back to your app.

typescript
import { SchoolsoftClient } from '@elias4044/ssp-node';

// Step 1 — generate the auth URL and PKCE material
const flow = SchoolsoftClient.startMobileFlow({
  school:      'engelska',
  redirectUri: 'com.myapp://auth',  // your deep-link URI
  orgid:       '18',
});

console.log(flow.authUrl);   // open this in a browser or WebView
// flow.verifier — store securely on device
// flow.state    — store to verify on callback (CSRF protection)

// After the user logs in, SchoolSoft redirects to:
//   com.myapp://auth?code=XXXX&state=YYYY
// Verify flow.state === YYYY, then:

// Step 2 — complete the flow with the code from the deep-link
const client = new SchoolsoftClient({ school: 'engelska' });
await client.completeMobileFlow(code, flow.verifier);

// Step 3 — exchange for session cookies
const info = await client.fetchMobileSessionInfo();
await client.mobileExchangeSession(info?.userId);

const news = await client.getNews();

startMobileFlow() — static method options

NameTypeRequiredDescription
schoolstringYesSchool slug, e.g. 'engelska'.
redirectUristringNoDeep-link redirect URI. Defaults to 'com.schoolsoftplus.app://'.
orgidstringNoSchool org ID. Defaults to '18'.

Returns MobileAuthFlowInit with fields:authUrl, verifier,state, school,orgid.

Token management

#Refreshing the access token

Access tokens are short-lived (≈ 15 minutes). Call mobileRefresh() to exchange the stored refresh token for a new access token. This is done automatically by mobileExchangeSession() if the token appears expired, but you can also call it manually.

typescript
// Manual refresh
await client.mobileRefresh();

// Check token state
console.log(client.accessToken);          // current access token string or null
console.log(client.refreshToken);         // current refresh token or null
console.log(client.isAccessTokenExpired); // boolean
console.log(client.hasMobileToken);       // true if an access token is present (any expiry)
mobileExchangeSession() automatically calls mobileRefresh() if isAccessTokenExpiredis true and a refresh token is available. You typically don't need to call it manually.

#Restoring tokens from storage

typescript
// After mobileLogin(), persist these values
const toSave = {
  accessToken:  client.accessToken,
  refreshToken: client.refreshToken,
};

// On next startup — restore and proceed without re-logging-in
const client2 = new SchoolsoftClient({ school: 'engelska' });
client2.setAccessToken(toSave.accessToken!, toSave.refreshToken ?? undefined);

// Exchange for session cookies (auto-refreshes if access token expired)
await client2.mobileExchangeSession();

Session management

#Verifying a session

verifySession()sends a lightweight request to SchoolSoft's /rest-api/session endpoint to check whether the current session cookies are still valid.

typescript
const alive = await client.verifySession();
// true  — session is valid and ready to use
// false — session has expired (need to re-authenticate)

#isAuthenticated

The isAuthenticated getter returns true if session cookies are set in memory — it does not verify with SchoolSoft. Use it as a quick guard before making requests, and verifySession() when you need a guaranteed live check.

typescript
if (!client.isAuthenticated) {
  await client.login({ username, password });
}

#Logout

logout() clears all in-memory state: session cookies, access token, and refresh token. It does not invalidate anything server-side.

typescript
client.logout();
console.log(client.isAuthenticated); // false

getSchools()

Returns every school registered on SchoolSoft, sorted alphabetically by name. Results are cached in-process for one hour to avoid hammering SchoolSoft on repeated calls. No authentication required.

typescript
const schools = await client.getSchools();
// Returns: School[]

// schools[0] might be:
// { id: 'engelska', name: 'Engelska Skolan Norr om Ström' }

// Find a school slug by name
const match = schools.find(s => s.name.toLowerCase().includes('carlwahren'));
console.log(match?.id); // 'carlwahren'

Return type: School[]

typescript
interface School {
  name: string;  // full display name
  id:   string;  // URL slug used in all API calls
}

getSession()

Returns the full session info for the currently authenticated user from SchoolSoft's /rest-api/session endpoint. Includes user identity, organisation, and user type.

typescript
const session = await client.getSession();

console.log(session.user.firstName);       // 'John'
console.log(session.user.lastName);        // 'Doe'
console.log(session.user.email);           // 'john.doe@school.se'
console.log(session.user.id);              // 12345
console.log(session.organization.name);   // 'Engelska Skolan'
console.log(session.userType);             // 'student'

Return type: SessionInfo

typescript
interface SessionInfo {
  user: {
    userName:  string;
    firstName: string;
    lastName:  string;
    email:     string;
    id:        number;
  };
  organization: {
    name: string;
    [key: string]: unknown;
  };
  userType: string;
  school:   string;
  [key: string]: unknown;
}

getLunch(week)

Returns the lunch menu for a given ISO week number. Each day contains a list of lunch options for that weekday.

typescript
const menu = await client.getLunch(22);  // week 22
// Returns: LunchMenu  (LunchDay[])

for (const day of menu) {
  console.log(day.weekDayName);                   // 'Monday', 'Tuesday', etc.
  console.log(day.lunchList?.map(l => l.name));   // ['Pasta Bolognese', 'Vegetarian option']
}

getLunch() — parameters

NameTypeRequiredDescription
weeknumberYesISO week number (1–53).

Return type: LunchMenu (alias for LunchDay[])

typescript
interface LunchDay {
  weekDay:     number;       // 1 = Monday … 5 = Friday
  weekDayName?: string;      // localised day name
  lunchList?:  LunchItem[];
  [key: string]: unknown;
}

interface LunchItem {
  name: string;
  [key: string]: unknown;
}

getSchedule(week?)

Returns the lesson schedule for the given week. Omitting week defaults to the current ISO week (calculated via the exported isoWeek(date) utility). Lessons are de-duplicated and sorted chronologically.

typescript
// Current week
const { lessons, week } = await client.getSchedule();
console.log(`Week ${week}: ${lessons.length} lessons`);

// Specific week
const { lessons: w22 } = await client.getSchedule(22);

// Each lesson
for (const lesson of lessons) {
  console.log(lesson.eventId);   // unique identifier
  console.log(lesson.startDate); // ISO 8601 date-time string
  console.log(lesson.endDate);   // ISO 8601 date-time string
  // Additional fields from SchoolSoft (subject name, room, teacher, etc.)
  // are passed through as-is under the [key: string]: unknown index signature
}

getSchedule() — parameters

NameTypeRequiredDescription
weeknumberNoISO week number (1–53). Defaults to current week.

Return type: { lessons: ScheduleLesson[]; week: number }

typescript
interface ScheduleLesson {
  eventId:   number | string;
  startDate: string;   // ISO 8601
  endDate:   string;   // ISO 8601
  [key: string]: unknown;
}
The library uses the ISO 8601 week algorithm for the isoWeek() helper, which matches how SchoolSoft numbers weeks (week 1 = first week with a Thursday in the new year). You can import it directly: import { isoWeek } from '@elias4044/ssp-node';

getAssignmentsForWeek(week?, year?)

Returns a flat list of all assignments due in a given week. Both parameters default to the current ISO week and year if omitted. Each object contains the assignment's id and title, plus any additional fields SchoolSoft includes. Pass the ID to getAssignment() to fetch full details.

typescript
// Current week (defaults)
const assignments = await client.getAssignmentsForWeek();

// Specific week and year
const assignments = await client.getAssignmentsForWeek(22, 2025);
// Returns: Assignment[]

for (const a of assignments) {
  console.log(a.id, a.title);
  // Fetch full details for one
  const detail = await client.getAssignment(a.id);
}

getAssignmentsForWeek() — parameters

NameTypeRequiredDescription
weeknumberNoISO week number. Defaults to current week.
yearnumberNoFull four-digit year. Defaults to current year.

Return type: Assignment[]

getAssignment(id, type?)

Fetches complete details for a single assignment or planning entry. Makes up to five parallel sub-requests to gather all related data (sections, connected plannings, assessment, grading). Returns nullfor sub-resources that don't exist (403/404 are handled gracefully).

typescript
// Default: fetch an assignment
const detail = await client.getAssignment(12345);

// Fetch a planning entry instead
const planning = await client.getAssignment(67890, 'planning');

// Full shape
console.log(detail.assignment);          // view object from SchoolSoft
console.log(detail.sections);            // sections array or null
console.log(detail.connectedPlannings);  // linked planning entries or null
console.log(detail.assessment?.review);              // teacher review text
console.log(detail.assessment?.teacherComment);      // teacher comment
console.log(detail.assessment?.studentComment);      // student comment
console.log(detail.assessment?.assessedCriteriaTabs); // graded criteria
console.log(detail.assessment?.partialMoments);       // partial assessment moments
console.log(detail.grading);             // grading object or null

getAssignment() — parameters

NameTypeRequiredDescription
idstring | numberYesThe assignment or planning ID.
type'assignment' | 'planning'NoEntry type. Defaults to 'assignment'.

Return type: AssignmentDetail

typescript
interface AssignmentDetail {
  assignment:         Record<string, unknown> | null;
  sections:           unknown[] | null;
  connectedPlannings: unknown[] | null;
  assessment: {
    review:              string;
    teacherComment:      string;
    studentComment:      string;
    partialMoments:      unknown[];
    assessedCriteriaTabs: unknown[];
  } | null;
  grading: unknown | null;
}

getCalendarEvents(week?)

Returns non-lesson calendar events for the given week — such as school holidays, custom reminders, and other entries that appear on the SchoolSoft calendar but are not regular lessons. Defaults to the current ISO week.

typescript
// Current week
const events = await client.getCalendarEvents();

// Specific week
const events = await client.getCalendarEvents(22);
// Returns: CalendarEvent[]

for (const ev of events) {
  console.log(ev.title);     // event title
  console.log(ev.startDate); // ISO 8601 string or undefined
  console.log(ev.endDate);   // ISO 8601 string or undefined
  console.log(ev.allDay);    // boolean — true for all-day entries
  console.log(ev.type);      // event type string from SchoolSoft
}

getCalendarEvents() — parameters

NameTypeRequiredDescription
weeknumberNoISO week number (1–53). Defaults to current week.

Return type: CalendarEvent[]

typescript
interface CalendarEvent {
  id?:        string | number;
  title?:     string;
  startDate?: string;   // ISO 8601
  endDate?:   string;   // ISO 8601
  allDay?:    boolean;
  type?:      string;
  [key: string]: unknown;
}

getSubjects()

Returns all subject rooms for the user, enriched with their entities (assignments and plannings), teachers, and the count of unread entities.

typescript
const subjects = await client.getSubjects();
// Returns: Subject[]

for (const sub of subjects) {
  console.log(sub.id);             // subject room ID
  console.log(sub.activityId);     // associated activity ID
  console.log(sub.unreadEntities); // number of unread items
  console.log(sub.teachers);       // array of teacher objects
  console.log(sub.entities);       // SubjectEntity[] — assignments & plannings
}

// Find entities that are assignments
const assignments = subjects
  .flatMap(s => s.entities)
  .filter(e => e.entityType === 'ASSIGNMENT');

Return type: Subject[]

typescript
interface Subject {
  activityId:     string;
  id:             string;
  entities:       SubjectEntity[];
  unreadEntities: number;
  teachers:       unknown[];
  [key: string]: unknown;
}

interface SubjectEntity {
  planningId?: string;
  entityType:  'PLANNING' | 'ASSIGNMENT';
  [key: string]: unknown;
}

getSubject(id)

Returns detailed information about a single subject room, including its examination overview, submission status, and all linked assignments.

typescript
const detail = await client.getSubject('123');

console.log(detail.subject);                   // subject metadata
console.log(detail.overview.examinations);     // upcoming examinations
console.log(detail.overview.submissions);      // pending submissions
console.log(detail.assignments);               // all assignments for the subject

getSubject() — parameters

NameTypeRequiredDescription
idstring | numberYesSubject room ID (from getSubjects()).

Return type: SubjectDetail

getNews()

Scrapes news items from the student startpage HTML. Returns an array of news items with an ID, title, and a short preview text.

typescript
const news = await client.getNews();
// Returns: NewsItem[]

for (const item of news) {
  console.log(item.id);      // news item ID (string) or null if not found
  console.log(item.title);   // news headline
  console.log(item.preview); // short preview text or null
}

Return type: NewsItem[]

getNewsDetail(id)

Fetches the full content of a single news article by its ID. Returns null if the article is not found.

typescript
const article = await client.getNewsDetail('42');
if (article) {
  console.log(article.id);           // '42'
  console.log(article.title);        // article headline
  console.log(article.body);         // raw HTML body of the article
  console.log(article.date);         // date string or null
  console.log(article.attachments);  // [{ name, url }, ...]
}

getNewsDetail() — parameters

NameTypeRequiredDescription
idstring | numberYesNews item ID (from getNews()).

Return type: NewsDetail | null

typescript
interface NewsDetail {
  id:          string;
  title:       string;
  body:        string;         // raw HTML
  date:        string | null;
  attachments: Array<{ name: string; url: string }>;
}
getNews(), getNewsDetail(), getStartpage(), and getClassStudents()scrape HTML pages rather than calling JSON endpoints. They depend on SchoolSoft's page structure and may break if SchoolSoft changes their HTML layout.

getStartpage()

Returns upcoming homework and recent test results from the student startpage by scraping the HTML. Useful for a quick daily overview.

typescript
const { homework, tests } = await client.getStartpage();

// Upcoming homework
for (const hw of homework) {
  console.log(hw.dateAndSubject); // e.g. 'Monday — Mathematics'
  console.log(hw.title);          // assignment/homework title
  console.log(hw.content);        // string[] — lines of the description
}

// Recent test results
for (const test of tests) {
  console.log(test.title);       // test name
  console.log(test.description); // grade / result text
  console.log(test.link);        // URL to the test detail page, if available
}

Return type: StartpageData

typescript
interface StartpageData {
  homework: StartpageHomework[];
  tests:    StartpageTest[];
}

interface StartpageHomework {
  dateAndSubject: string;
  title:          string;
  content:        string[];
}

interface StartpageTest {
  title:       string;
  description: string;
  link:        string | undefined;
}

getClassStudents()

Returns the list of students in the authenticated user's class. Scrapes the student directory HTML page.

typescript
const students = await client.getClassStudents();
// Returns: ClassStudent[]

for (const s of students) {
  console.log(s.name);    // full display name
  console.log(s.email);   // email address, or null
  console.log(s.address); // home address, or null
}

Return type: ClassStudent[]

typescript
interface ClassStudent {
  name:    string;
  email:   string | null;
  address: string | null;
}

getInbox()

Returns the list of messages in the authenticated user's inbox.

typescript
const messages = await client.getInbox();
// Returns: Message[]

for (const msg of messages) {
  console.log(msg.id);         // message ID
  console.log(msg.subject);    // message subject line
  console.log(msg.senderName); // display name of the sender
  console.log(msg.date);       // date string from SchoolSoft
  console.log(msg.read);       // boolean — whether the message has been read
}

Return type: Message[]

getOutbox()

Returns the list of messages the authenticated user has sent.

typescript
const sent = await client.getOutbox();
// Returns: Message[]

Return type: Message[]

typescript
interface Message {
  id:          string | number;
  subject:     string;
  senderName?: string;
  senderId?:   string | number;
  date?:       string;
  read?:       boolean;
  [key: string]: unknown;
}

getMessage(id)

Fetches the full content of a single message, including its body, recipients, and any attachments. Returns null if not found.

typescript
const detail = await client.getMessage(42);
if (detail) {
  console.log(detail.subject);       // subject line
  console.log(detail.body);          // full message body text
  console.log(detail.recipients);    // MessageRecipient[]
  console.log(detail.attachments);   // MessageAttachment[]
}

getMessage() — parameters

NameTypeRequiredDescription
idstring | numberYesMessage ID (from getInbox() or getOutbox()).

Return type: MessageDetail | null

typescript
interface MessageDetail extends Message {
  body:          string;
  recipients?:   MessageRecipient[];
  attachments?:  MessageAttachment[];
  [key: string]: unknown;
}

interface MessageRecipient {
  id:    string | number;
  name:  string;
  type?: string;
}

interface MessageAttachment {
  id:   string | number;
  name: string;
  url?: string;
}

sendMessage(options)

Sends a message to one or more recipients.

typescript
const result = await client.sendMessage({
  subject:    'Hello from ssp-node',
  body:       'This message was sent programmatically.',
  recipients: [
    { id: 12345, type: 'student' },
    { id: 67890, type: 'staff'   },
  ],
});

console.log(result.success); // true
console.log(result.status);  // HTTP status code

sendMessage() — options

NameTypeRequiredDescription
subjectstringYesMessage subject line.
bodystringYesMessage body text.
recipientsArray<{ id: string | number; type?: string }>YesList of recipient objects with numeric IDs.

Return type: { success: boolean; status: number }

markMessageRead(id)

Marks a message as read. Returns true on success.

typescript
const ok = await client.markMessageRead(42);
console.log(ok); // true

markMessageRead() — parameters

NameTypeRequiredDescription
idstring | numberYesMessage ID.

getAttendance()

Returns the full attendance record for the authenticated student — one entry per lesson, indicating whether the student was present, absent, or late.

typescript
const records = await client.getAttendance();
// Returns: AbsenceRecord[]

for (const r of records) {
  console.log(r.date);        // date string
  console.log(r.subjectName); // subject name
  console.log(r.status);      // 'present' | 'absent' | 'late' | string
  console.log(r.lessonId);    // lesson identifier
}

Return type: AbsenceRecord[]

typescript
interface AbsenceRecord {
  id?:          string | number;
  date?:        string;
  lessonId?:    string | number;
  subjectName?: string;
  status?:      'present' | 'absent' | 'late' | string;
  [key: string]: unknown;
}

getAbsenceSummary()

Returns an aggregated attendance summary, grouped by subject. Includes total lessons, total absences, and an overall attendance rate percentage.

typescript
const summary = await client.getAbsenceSummary();

console.log(summary.totalLessons);    // e.g. 200
console.log(summary.totalAbsences);   // e.g. 12
console.log(summary.attendanceRate);  // e.g. 94 (percentage 0–100)

for (const sub of summary.subjects) {
  console.log(sub.subject);       // subject name
  console.log(sub.totalLessons);  // total lessons for this subject
  console.log(sub.absences);      // absences for this subject
}

Return type: AttendanceSummary

typescript
interface AttendanceSummary {
  totalLessons:   number;
  totalAbsences:  number;
  attendanceRate: number;  // 0–100 percentage
  subjects: Array<{
    subject:      string;
    totalLessons: number;
    absences:     number;
  }>;
}

getGrades()

Returns all individual grade entries for the authenticated student.

typescript
const grades = await client.getGrades();
// Returns: GradeEntry[]

for (const g of grades) {
  console.log(g.subjectName);  // e.g. 'Mathematics'
  console.log(g.grade);        // e.g. 'A', 'B', 'E'
  console.log(g.date);         // date the grade was set
  console.log(g.teacherName);  // grading teacher
  console.log(g.comment);      // optional comment
}

Return type: GradeEntry[]

typescript
interface GradeEntry {
  id?:          string | number;
  subjectName?: string;
  activityId?:  string | number;
  grade?:       string;
  date?:        string;
  teacherName?: string;
  comment?:     string;
  [key: string]: unknown;
}

getGradeOverview()

Returns a grade overview grouped by subject, including examinations and submissions for each subject room.

typescript
const overview = await client.getGradeOverview();
// Returns: GradeOverview[]

for (const sub of overview) {
  console.log(sub.subjectName);    // subject display name
  console.log(sub.activityId);     // activity ID
  console.log(sub.examinations);   // examination records
  console.log(sub.submissions);    // submission records
}

Return type: GradeOverview[]

typescript
interface GradeOverview {
  activityId:   string;
  subjectName:  string;
  examinations: Record<string, unknown>[];
  submissions:  Record<string, unknown>[];
}

Advanced — Functional API

Every function used internally by SchoolsoftClient is also exported at the top level. This is useful for serverless functions, functional-style code, or when you want to manage session state yourself.

typescript
import {
  simpleLogin,
  mobileLogin,
  mobileGetSession,
  fetchMobileSession,
  mobileRefreshToken,
  verifySession,
  getSession,
  getSchools,
  getLunch,
  getSchedule,
  getAssignmentsForWeek,
  getAssignment,
  getCalendarEvents,
  getSubjects,
  getSubject,
  getNews,
  getNewsDetail,
  getStartpage,
  getClassStudents,
  getInbox,
  getOutbox,
  getMessage,
  sendMessage,
  markMessageRead,
  getAttendance,
  getAbsenceSummary,
  getGrades,
  getGradeOverview,
  isoWeek,
} from '@elias4044/ssp-node';

// ── Example: simple login + schedule ──
const school  = 'engelska';
const session = await simpleLogin(school, { username: 'john.doe', password: 'secret' });
const { lessons } = await getSchedule(school, session.cookieHeader, isoWeek(new Date()), 'my-agent/1.0');

// ── Example: mobile login ──
const tokens  = await mobileLogin(school, { username: 'john.doe', password: 'secret' });
const info    = await fetchMobileSession(school, tokens.accessToken);
const cookies = await mobileGetSession(school, tokens.accessToken, info?.userId);
const news    = await getNews(school, cookies.cookieHeader, 'my-agent/1.0');

All data functions share the same signature pattern:

typescript
getXxx(
  school:       string,        // school slug, e.g. 'engelska'
  cookieHeader: string,        // 'JSESSIONID=...; hash=...; usertype=1'
  ...args,                     // endpoint-specific arguments
  userAgent:    string,        // User-Agent header value
): Promise<ReturnType>

Advanced — client.raw()

Makes a raw authenticated request to any SchoolSoft endpoint using the client's stored session cookies. Useful for endpoints not yet covered by the library. The path can be an absolute URL or a root-relative path (starting with /).

typescript
// Root-relative path — the school slug is prepended automatically
const result = await client.raw('/rest-api/student/some/endpoint', {
  method:       'GET',
  responseType: 'json',
});
console.log(result.status, result.data);

// Absolute URL — used as-is
const result2 = await client.raw<MyType>(
  'https://sms.schoolsoft.se/engelska/rest-api/student/endpoint',
  { responseType: 'json' }
);

Advanced — Raw requests (schoolsoftFetch)

schoolsoftFetch is the underlying HTTP client used by every function in the library. It automatically sets the correct Origin, Referer, and User-Agent headers required by SchoolSoft, and parses the response according to the requested responseType.

typescript
import { schoolsoftFetch, ssUrl } from '@elias4044/ssp-node';

// ssUrl() builds a correctly structured SchoolSoft URL
const url = ssUrl('engelska', '/rest-api/student/calendar/lessons/week/22');
// → 'https://sms.schoolsoft.se/engelska/rest-api/student/calendar/lessons/week/22'

const result = await schoolsoftFetch<MyResponseType>(url, 'engelska', {
  method:       'GET',
  headers:      { Cookie: cookieHeader, Accept: 'application/json' },
  responseType: 'json',       // 'json' | 'text' | 'buffer'
  followRedirects: true,      // default true — set false to capture 302s
}, 'my-app/1.0');             // optional User-Agent

console.log(result.status);      // HTTP status code
console.log(result.data);        // parsed body (type T)
console.log(result.headers);     // lowercase key map of all response headers
console.log(result.setCookies);  // string[] — raw Set-Cookie values

For even lower-level access, rawRequest() is the underlying Node https wrapper used by schoolsoftFetch. It follows redirects (up to 5 hops), preserves all Set-Cookie headers, and returns the raw Buffer body.

typescript
import { rawRequest } from '@elias4044/ssp-node';

const raw = await rawRequest('https://sms.schoolsoft.se/engelska/rest-api/session', {
  method:          'GET',
  headers:         { Cookie: cookieHeader },
  followRedirects: false,
  responseType:    'json',
});

console.log(raw.status);     // HTTP status
console.log(raw.body);       // Buffer — parse yourself
console.log(raw.setCookies); // string[]

Advanced — PKCE utilities

The PKCE helpers used internally by the mobile auth flow are exported for use in your own OAuth2 implementations. All crypto uses Node's built-in crypto module — no external dependencies.

typescript
import { makePkcePair, makeState, base64url } from '@elias4044/ssp-node';

// Generate a PKCE verifier + SHA-256 challenge pair
const { verifier, challenge } = makePkcePair();
// verifier  — random 32-byte base64url string, kept secret on device
// challenge — SHA-256(verifier) encoded as base64url, sent to auth server

// Generate a random CSRF state token
const state = makeState();
// → 24-char hex string, e.g. 'a3f91c7e2b84d06f1234abcd'

// base64url encode arbitrary data
const encoded = base64url(Buffer.from('hello world'));

Advanced — Token storage pattern

For apps that need to survive restarts without the user logging in again — mobile apps, daemons, bots — persist the refresh token and restore it on startup.

typescript
import { SchoolsoftClient } from '@elias4044/ssp-node';
import fs from 'fs';

const TOKEN_FILE = '.ssp-tokens.json';
const school = 'engelska';

async function getAuthenticatedClient(): Promise<SchoolsoftClient> {
  const client = new SchoolsoftClient({ school });

  // Try to restore saved tokens
  if (fs.existsSync(TOKEN_FILE)) {
    const saved = JSON.parse(fs.readFileSync(TOKEN_FILE, 'utf8'));
    client.setAccessToken(saved.accessToken, saved.refreshToken, saved.expiresAt);

    try {
      // This auto-refreshes if access token is expired
      await client.mobileExchangeSession();

      if (await client.verifySession()) {
        console.log('Session restored from file.');
        return client;
      }
    } catch {
      console.log('Saved session invalid — re-logging in.');
    }
  }

  // Fall back to full login
  const result = await client.mobileLogin({
    username: process.env.SS_USER!,
    password: process.env.SS_PASS!,
  });

  // Persist tokens for next run
  fs.writeFileSync(TOKEN_FILE, JSON.stringify({
    accessToken:  result.accessToken,
    refreshToken: result.refreshToken,
    expiresAt:    result.expiresAt,
  }));

  await client.mobileExchangeSession();
  return client;
}

const client = await getAuthenticatedClient();
const lunch  = await client.getLunch(22);
Never commit token files to source control. Add .ssp-tokens.json to your .gitignore. In production, use a secrets manager or environment variables with encrypted storage.

Advanced — Error handling

All errors thrown by the library extend the base SchoolsoftError class, which carries the HTTP status code and endpoint URL. This lets you catch and inspect errors programmatically without relying on message strings.

typescript
import {
  SchoolsoftError,
  AuthenticationError,
  SessionExpiredError,
  NotFoundError,
  RateLimitError,
  NetworkError,
  ParseError,
} from '@elias4044/ssp-node';

try {
  await client.getSchedule(22);
} catch (err) {
  if (err instanceof SessionExpiredError) {
    // Session timed out — re-authenticate
    await client.login({ username, password });
  } else if (err instanceof RateLimitError) {
    const wait = err.retryAfter ?? 60;
    console.warn(`Rate limited — retry in ${wait}s`);
  } else if (err instanceof AuthenticationError) {
    console.error('Not logged in or access denied:', err.message);
  } else if (err instanceof NetworkError) {
    console.error('Network failure:', err.originalError);
  } else if (err instanceof SchoolsoftError) {
    // Catch-all for any other library error
    console.error(`[${err.statusCode}] ${err.endpoint}: ${err.message}`);
  }
}

The full error hierarchy:

ClassExtendsWhen thrown
SchoolsoftErrorErrorBase class. Carries statusCode and endpoint.
AuthenticationErrorSchoolsoftErrorUnauthenticated call or SchoolSoft returns 401/403.
SessionExpiredErrorSchoolsoftErrorSession cookies are present but the session has expired.
NotFoundErrorSchoolsoftErrorSchoolSoft returns 404 for a resource.
RateLimitErrorSchoolsoftErrorSchoolSoft returns 429. Has optional retryAfter (seconds).
NetworkErrorSchoolsoftErrorConnection refused, timeout, or other transport failure.
ParseErrorSchoolsoftErrorResponse shape was unexpected and parsing failed.

Types reference

All types are exported from the package root. Import them as named type imports:

typescript
import type {
  SchoolsoftClientOptions,
  RetryOptions,
  SimpleLoginResult,
  MobileLoginResult,
  MobileAuthFlowInit,
  MobileSessionResult,
  MobileSessionOptions,
  SessionInfo, SessionUser, SessionOrganization,
  School,
  LunchMenu, LunchDay, LunchItem,
  ScheduleLesson,
  CalendarEvent,
  Assignment, AssignmentDetail,
  Subject, SubjectEntity, SubjectDetail,
  NewsItem, NewsDetail,
  StartpageData, StartpageHomework, StartpageTest,
  ClassStudent,
  Message, MessageDetail, MessageRecipient, MessageAttachment, SendMessageOptions,
  AbsenceRecord, AttendanceSummary,
  GradeEntry, GradeOverview,
} from '@elias4044/ssp-node';

Client options

Options passed to new SchoolsoftClient(options):

SchoolsoftClientOptions

NameTypeRequiredDescription
schoolstringNoSchool slug from the URL (between sms.schoolsoft.se/ and the next /). Defaults to 'engelska'. Use getSchools() to find yours.
userAgentstringNoCustom User-Agent header sent with all requests.
debugbooleanNoEmit debug messages to stderr. Defaults to false.
retryboolean | RetryOptionsNoAutomatic retry on transient failures. Pass true for defaults, false to disable, or a RetryOptions object. Enabled by default with 3 attempts.
cachefalse | { schools?: number }NoCache config. Set false to disable, or pass an object with a schools TTL in milliseconds (default 3 600 000 = 1 hour).
onSessionExpired() => void | Promise<void>NoCallback invoked when a SessionExpiredError is detected. Use it to trigger re-authentication in long-running processes.
typescript
// RetryOptions — all fields optional
interface RetryOptions {
  maxAttempts?:   number;    // total attempts including the first. Default: 3
  initialDelay?:  number;    // ms before the first retry. Default: 500
  backoffFactor?: number;    // multiplier after each attempt. Default: 2
  maxDelay?:      number;    // upper bound on delay in ms. Default: 10 000
  retryOn?:       number[];  // HTTP status codes to retry. Default: [429, 502, 503, 504]
}

The school slug is the only meaningful piece of configuration for most use cases. You can find your school's slug by visiting https://sms.schoolsoft.se and looking at the URL after you select your school, or by calling getSchools():

typescript
const schools = await client.getSchools();
const mine = schools.find(s => s.name.toLowerCase().includes('my school name'));
console.log(mine?.id); // e.g. 'carlwahren'

const client2 = new SchoolsoftClient({ school: mine!.id });

All exports

Complete list of everything exported by @elias4044/ssp-node:

typescript
// Main class
export { SchoolsoftClient } from './client';

// Auth helpers (functional)
export { simpleLogin } from './auth/simple';
export {
  startMobileFlow, mobileLogin, completeMobileFlow,
  mobileRefreshToken, mobileGetSession, fetchMobileSession,
  verifySession, exchangeCodeForToken,
} from './auth/mobile';

// API functions (functional)
export { getSession }                           from './api/session';
export { getSchools }                           from './api/schools';
export { getLunch }                             from './api/lunch';
export { getSchedule, isoWeek }                 from './api/schedule';
export { getAssignmentsForWeek, getAssignment } from './api/assignments';
export { getCalendarEvents }                    from './api/calendar';
export { getSubjects, getSubject }              from './api/subjects';
export { getNews }                              from './api/news';
export { getNewsDetail }                        from './api/newsdetail';
export { getStartpage }                         from './api/startpage';
export { getClassStudents }                     from './api/people';
export {
  getInbox, getOutbox, getMessage,
  sendMessage, markMessageRead,
}                                               from './api/messages';
export { getAttendance, getAbsenceSummary }     from './api/attendance';
export { getGrades, getGradeOverview }          from './api/grades';

// Utilities
export { makePkcePair, makeState, base64url }   from './utils/pkce';
export {
  schoolsoftFetch, rawRequest, ssUrl, extractCookie,
  parseCookieValue, SS_ORIGIN, DEFAULT_UA,
}                                               from './utils/http';
export { decodeLatin1, parseHtml }              from './utils/html';
export { Cache }                                from './utils/cache';
export { withRetry }                            from './utils/retry';
export {
  isoWeek as isoWeekUtil, isoYear, weekRange,
  toDateString, parseSchoolsoftDate, weekDayName,
  currentWeek, currentYear, isWeekend, schoolDays,
}                                               from './utils/date';

// Typed error classes
export {
  SchoolsoftError, AuthenticationError, SessionExpiredError,
  NotFoundError, RateLimitError, NetworkError, ParseError,
}                                               from './utils/errors';

// All types
export type {
  SchoolsoftClientOptions, RetryOptions,
  SimpleLoginResult, MobileLoginResult,
  MobileAuthFlowInit, MobileSessionResult, MobileSessionOptions,
  SessionInfo, SessionUser, SessionOrganization,
  School, LunchMenu, LunchDay, LunchItem,
  ScheduleLesson, CalendarEvent,
  Assignment, AssignmentDetail,
  Subject, SubjectEntity, SubjectDetail,
  NewsItem, NewsDetail,
  StartpageData, StartpageHomework, StartpageTest,
  ClassStudent,
  Message, MessageDetail, MessageRecipient, MessageAttachment, SendMessageOptions,
  AbsenceRecord, AttendanceSummary,
  GradeEntry, GradeOverview,
} from './types';