Fresh TypeScript SDK and CLI for the Librus family portal and Gateway API 2.0 JSON flows.
For Portal accounts, this project logs into portal.librus.pl, loads linked
child accounts from portal/api/v3/SynergiaAccounts, and uses the selected
child account's bearer token against https://api.librus.pl/3.0.
For school-issued accounts, it can also log in with the Gateway API 2.0 login
and password, keep the resulting Synergia cookies, and read
https://synergia.librus.pl/gateway/api/2.0. That backend is already scoped to
one account and does not support Portal-only child discovery.
It intentionally does not reuse the legacy synergia.librus.pl HTML-scraping
approach.
This implementation was inspired by Mati365/librus-api.
Report vulnerabilities privately as described in SECURITY.md.
Never commit, log, or print real credentials, bearer tokens, cookies, or other secrets in source files, fixtures, examples, or documentation.
The repository also publishes a weekly OpenSSF Scorecard report from GitHub Actions so maintainers can track workflow hardening and other supply-chain signals over time.
This package requires Node.js 22 or newer.
Install the package from npm:
npm install librus-sdkUse the CLI without installing it globally:
npm exec --package librus-sdk -- librus-sdk --version
npm exec --package librus-sdk -- librus-sdk children list
npm exec --package librus-sdk -- librus-sdk grades list --child <id-or-login>The canonical CLI binary is librus-sdk. A deprecated librus alias remains
temporarily for compatibility and prints a warning before delegating.
The package also ships a generated openapi.json for the
SDK-supported child-scoped https://api.librus.pl/3.0 surface, so non-TypeScript
consumers can generate clients in other languages. It is also exported as
librus-sdk/openapi.json.
This README is the canonical public-interface reference for the npm package and CLI published from this repository.
LibrusSession.fromEnv() and the CLI read credentials and timeout settings from
these variables:
| Variable | Required | Purpose |
|---|---|---|
LIBRUS_API_BACKEND |
No | Optional api_v3 or gateway_api_20 selector. |
LIBRUS_PORTAL_EMAIL |
api_v3 |
Portal login email used for portal.librus.pl. |
LIBRUS_PORTAL_PASSWORD |
api_v3 |
Portal login password used for portal.librus.pl. |
LIBRUS_GATEWAY_LOGIN |
gateway_api_20 |
School-issued Gateway API 2.0 login. This is not an email address. |
LIBRUS_GATEWAY_PASSWORD |
gateway_api_20 |
Password for the Gateway API 2.0 login. |
LIBRUS_CHILD |
No | Optional default child id or login for api_v3 CLI commands. |
LIBRUS_TIMEOUT_MS |
No | Positive integer request timeout in milliseconds. |
If no timeout is configured, portal and child-scoped SDK requests default to
30000 milliseconds. Invalid timeout values fail fast with
CONFIGURATION_ERROR.
When no LIBRUS_API_BACKEND is set, complete Portal credentials select
api_v3. If Portal credentials are absent and complete Gateway credentials are
present, gateway_api_20 is selected. If neither set is complete, the default
is api_v3 so missing-credential errors stay Portal-oriented. When both
credential sets are complete, api_v3 wins unless
LIBRUS_API_BACKEND=gateway_api_20 is set explicitly.
Empty credential variables are treated as invalid. If a portal-prefixed
credential variable is set but empty, the deprecated compatibility alias for
that credential is ignored. LIBRUS_CHILD is used only by api_v3; CLI
--child wins over LIBRUS_CHILD, and explicit --child is rejected for
gateway_api_20 because that backend is already scoped to one account.
Import from the package root:
import {
BffApiClient,
GatewayApi20AuthClient,
LibrusSession,
PortalClient,
SynergiaApiClient,
generateOpenApiDocument,
LibrusSdkError,
type LibrusApiBackend,
} from "librus-sdk";| Export | Purpose |
|---|---|
LibrusSession |
Recommended high-level entry point. Handles login, linked-child discovery, child selection, and creation of child-scoped API clients. |
BffApiClient |
Experimental child-scoped client for selected https://testbff.librus.pl/v1 mobile backend reads. |
GatewayApi20AuthClient |
Lower-level Gateway API 2.0 login/password auth client for synergia.librus.pl/gateway/api/2.0 cookie-backed requests. |
PortalClient |
Lower-level portal session client for portal.librus.pl login, /Me, and /SynergiaAccounts. |
SynergiaApiClient |
Child-scoped GET client for the supported https://api.librus.pl/3.0 surface when you already have a bearer token. |
generateOpenApiDocument |
Generates the shipped OpenAPI document for the supported child-scoped GET subset. |
Recommended session flow:
import { LibrusSession } from "librus-sdk";
const session = LibrusSession.fromEnv();
const children = await session.listChildren();
const client = await session.forChild(children[0]);
const grades = await client.getGrades();
console.log(grades);Gateway API 2.0 flow:
LIBRUS_API_BACKEND=gateway_api_20
LIBRUS_GATEWAY_LOGIN=1234567
LIBRUS_GATEWAY_PASSWORD=your-password
npm exec --package librus-sdk -- librus-sdk grades listimport { LibrusSession } from "librus-sdk";
const session = LibrusSession.fromGatewayCredentials({
login: "1234567",
password: "your-password",
});
const client = await session.forChild();
const grades = await client.getGrades();
console.log(grades);Gateway API 2.0 is already child-scoped. getPortalMe(),
getSynergiaAccounts(), listChildren(), resolveChild(...),
forChildBff(...), and forChildWiadomosci(...) require Portal auth and fail
with UNSUPPORTED_BACKEND in gateway_api_20.
Current high-level methods on LibrusSession:
LibrusSession.fromEnv(env?)LibrusSession.fromGatewayCredentials(credentials, options?)new LibrusSession({ apiBackend?, credentials, portalClient?, portalClientOptions?, gatewayApi20AuthClient?, gatewayApi20AuthClientOptions?, bffClientOptions?, synergiaClientOptions?, wiadomosciClientOptions?, requestTimeoutMs? })login()getApiBackend()getPortalMe()getSynergiaAccounts()listChildren()resolveChild(selector)forChild(selectorOrChild)forChildWiadomosci(selectorOrChild)forChildBff(selectorOrChild)
Current methods on PortalClient:
new PortalClient(options?)login(credentials)getMe()getSynergiaAccounts()isLoggedIn()
SynergiaApiClient exposes explicit child-scoped read methods grouped by
endpoint family rather than raw route strings:
| Domain | Methods exposed today |
|---|---|
| Profile | getMe() |
| Grades | getGrades(), getGradeAverages(subjectId?), getGradeCategories(), getGradeComments(), getBehaviourGrades(), getBehaviourGradeTypes(), getBehaviourGradePoints(), getBehaviourPointCategories(), getBehaviourPointComments(), getBehaviourSystemProposal(), getDescriptiveGrades(), getDescriptiveGradeComments(), getDescriptiveGradeSkills(), getDescriptiveGradeText(subjectId), getDescriptiveGradeTextCategories(), getDescriptiveTextGrades(), getDescriptiveTextGradeSkills(), getPointGrades(subjectId?), getPointGradeAverages(subjectId?), getPointGradeCategories(), getPointGradeComments(), getTextGrades() |
| Attendance | getAttendances(), getAttendanceTypes() |
| Timetable | getTimetableWeek(weekStart), getTimetableDay(day), getTimetableEntry(id), getCalendars(), getClassFreeDays(), getClassFreeDayTypes(), getSchoolFreeDays(), getTeacherFreeDays(), getSubstitution(id), getVirtualClasses() |
| Lessons | listLessons(), getLesson(id), listPlannedLessons(), getPlannedLesson(id), getPlannedLessonAttachment(id), listRealizations(), getRealization(id) |
| Homework | getHomeWorks(), getHomeworkAssignments(), getHomeworkAssignmentAttachment(id), getHomeworkCategories() |
| Messages | listMessages(options?), getMessage(id), getUnreadMessages(), listMessagesForUser(userId), listMessageReceiverGroups(), getMessageReceiverGroup(id), getMessageAttachment(id). listMessages supports afterId, page, limit, alternativeBody, changeNewLine, and getAllTypes query options. |
| Announcements and notes | listSchoolNotices(), getSchoolNotice(id), listNotes(), getNote(id), listNoteCategories() |
| School metadata | getSchool(), getSchoolById(id), getClass(), getClassroom(id), listSubjects(), getSubject(id), listUsers(), getUser(id) |
| Notifications and justifications | getLuckyNumber(forDay?), getNotificationCenter(), getPushConfigurations(), listJustifications(options?), getJustification(id), listParentTeacherConferences(), getSystemData() |
| Auth reads | listAuthPhotos(), getAuthPhoto(id), getAuthUserInfo(id), getAuthTokenInfo(), getAuthClassroom(id) |
Attachment-style methods such as getHomeworkAssignmentAttachment(id),
getMessageAttachment(id), and getPlannedLessonAttachment(id) return
SynergiaBinaryResult with { data, contentType, contentDisposition }.
When a SynergiaApiClient is created through api_v3
LibrusSession.forChild(child), reads use bearer-token requests against
https://api.librus.pl/3.0 by default. When it is created through
gateway_api_20 with LibrusSession.forChild() and no child selector, reads
use cookie-backed requests against
https://synergia.librus.pl/gateway/api/2.0 instead; API 3.0 bearer requests
are not expected to work for that backend.
Use LibrusSession.forChildWiadomosci() to opt in to the portal-authenticated
https://wiadomosci.librus.pl/api inbox backend for listMessages(),
getMessage(id), and getUnreadMessages(). Other child-scoped reads, message
receiver groups, and message attachment downloads continue to use the session's
default Synergia transport. Direct new SynergiaApiClient(token) construction
keeps using API 3.0 for all methods unless lower-level transport options or a
custom message backend are supplied.
BffApiClient currently exposes the research-oriented listMessages() method
for GET https://testbff.librus.pl/v1/Messages. It uses the selected child's
Synergia access token as x-zse-authorization and returns the raw
{ inboxMessages } BFF response shape.
getAuthPhoto(id) mirrors the live API and returns JSON with the photo payload
under data.photo, including base64 content in data.photo.content. The CLI
auth photo --output <path> command decodes that content before writing the
file.
HomeWork.LessonNo mirrors the live Librus payload and may be string,
number, or null. HomeWork.Subject may be omitted entirely or returned as
null.
These tables describe the option objects currently accepted by the exported
constructors and helpers. The interface names are the current source names used
in the generated .d.ts, but the package documents the accepted property shapes
here rather than promising every helper type as a named top-level export.
LibrusSession constructor options (LibrusSessionOptions):
| Property | Required | Meaning |
|---|---|---|
credentials |
Yes | Portal { email, password } or Gateway { login, password } credentials. |
apiBackend |
No | api_v3 by default, or gateway_api_20 for Gateway API 2.0 credentials. |
portalClient |
No | Reuse an existing PortalClient instance instead of constructing one internally. |
portalClientOptions |
No | Passed to the internally created PortalClient when portalClient is not supplied. |
gatewayApi20AuthClient |
No | Reuse an existing Gateway API 2.0 auth client in gateway_api_20. |
gatewayApi20AuthClientOptions |
No | Passed to the internally created Gateway API 2.0 auth client. |
bffClientOptions |
No | Passed to BFF clients created by forChildBff(...). |
synergiaClientOptions |
No | Passed to SynergiaApiClient instances created by forChild(...). |
wiadomosciClientOptions |
No | Passed to wiadomosci-backed clients created by forChildWiadomosci(...). |
requestTimeoutMs |
No | Session-wide default timeout for internally created portal and child clients. |
PortalClient constructor options (PortalClientOptions):
| Property | Required | Meaning |
|---|---|---|
fetch |
No | Custom fetch implementation. The client wraps it in cookie-backed session handling. |
portalBaseUrl |
No | Portal origin. Defaults to https://portal.librus.pl. |
portalApiBaseUrl |
No | Portal API base URL. Defaults to ${portalBaseUrl}/api/v3. |
loginPath |
No | Login page path. Defaults to /konto-librus/login. |
loginActionPath |
No | Login form submission path. Defaults to /konto-librus/login/action. |
requestTimeoutMs |
No | Positive integer timeout in milliseconds. Defaults to 30000. |
SynergiaApiClient constructor options (SynergiaApiClientOptions):
| Property | Required | Meaning |
|---|---|---|
fetch |
No | Custom fetch implementation for child-scoped API requests. |
apiBaseUrl |
No | Synergia API base URL. Defaults to https://api.librus.pl/3.0. |
authMode |
No | bearer by default, or cookie for Gateway-created sessions. |
requestTimeoutMs |
No | Positive integer timeout in milliseconds. Defaults to 30000. |
generateOpenApiDocument() options (GenerateOpenApiDocumentOptions):
| Property | Required | Meaning |
|---|---|---|
title |
No | Overrides the generated OpenAPI info.title. |
version |
No | Overrides the generated OpenAPI info.version. Pass the package version when generating a publishable document. |
serverUrl |
No | Overrides the default server URL https://api.librus.pl/3.0. |
In addition to the top-level classes above, the package root re-exports the
model and error modules under src/sdk/models/.
| Category | Public surface |
|---|---|
| Portal and child-account types | PortalCredentials, PortalMe, ChildAccount, SynergiaAccountsResponse |
| Shared response helpers | ApiRef, SynergiaBinaryResult, other common helper interfaces used by SDK responses |
| Synergia response models | Response and entity interfaces for the supported child-scoped API surface under the synergia model barrels |
| Error classes | LibrusSdkError, LibrusApiError, LibrusAuthenticationError, LibrusConfigurationError, LibrusNetworkTimeoutError, LibrusPortalPageError, LibrusResponseValidationError |
Root CLI commands:
librus-sdk --helplibrus-sdk --version
Every leaf command supports --format <text|json>. In api_v3, child-scoped
commands require a child selector: pass --child <id-or-login> or set
LIBRUS_CHILD. --child wins when both are present. In gateway_api_20, the
account is already child-scoped, so the same commands run without --child and
explicit --child fails with UNSUPPORTED_BACKEND. Portal-only commands such
as children list, messages bff-list, and messages wiadomosci-list are not
available in gateway_api_20.
| Family | Subcommands | Extra selectors and flags |
|---|---|---|
children |
list |
Portal-only; no child selector. |
me |
root command | api_v3 requires a child selector; gateway_api_20 does not. |
grades |
list |
api_v3 requires a child selector; gateway_api_20 does not. |
attendance |
list |
api_v3 requires a child selector; gateway_api_20 does not. |
homework |
list |
api_v3 requires a child selector; gateway_api_20 does not. |
messages |
list, bff-list, get, unread, wiadomosci-list, wiadomosci-get, wiadomosci-unread |
list, get, and unread support --backend <api3|wiadomosci> and default to api3; get and wiadomosci-get require --id <id>; bff-* and wiadomosci-* are Portal-only. |
timetable |
week, day, entry |
week requires --week-start <YYYY-MM-DD>; day requires --day <YYYY-MM-DD>; entry requires --id <id>; api_v3 requires a child selector; gateway_api_20 does not. |
announcements |
list, get |
get requires --id <id>; api_v3 requires a child selector; gateway_api_20 does not. |
notes |
list, get |
get requires --id <id>; api_v3 requires a child selector; gateway_api_20 does not. |
lessons |
list, get, planned-list, planned-get, planned-attachment, realizations-list, realizations-get |
get, planned-get, and realizations-get require --id <id>; planned-attachment requires --id <id> and --output <path>; api_v3 requires a child selector; gateway_api_20 does not. |
lucky-number |
get |
Optional --for-day <YYYY-MM-DD>; api_v3 requires a child selector; gateway_api_20 does not. |
notifications |
center, push-configurations |
api_v3 requires a child selector; gateway_api_20 does not. |
justifications |
list, get, conferences, system-data |
list supports --date-from <YYYY-MM-DD>; get requires --id <id>; api_v3 requires a child selector; gateway_api_20 does not. |
auth |
photos, photo, user-info, token-info, classroom |
photo requires --id <id> and --output <path>; user-info and classroom require --id <id>; api_v3 requires a child selector for child-scoped auth commands; gateway_api_20 does not. |
- Human-readable text is the default stdout format for successful commands.
--format jsonis the stable machine-readable interface for automation.- Errors are written to stderr and follow the selected output format.
- Non-successful commands return a non-zero exit code.
children listJSON output is{ lastModification, children }.- Child-scoped read commands emit
{ child, data }in JSON output forapi_v3and{ data }forgateway_api_20, where the session is already child-scoped. - Download commands such as
lessons planned-attachmentandauth photowrite the requested file first, then report saved-file metadata instead of raw bytes. JSON output keeps the same{ child, data }envelope, wheredatacontains{ path, bytes, contentType, contentDisposition }. - Text output is meant for terminal readability. It is not a stable parsing contract.
- Current text rendering normalizes epoch-style
*Datefields to localYYYY-MM-DD HH:mm:ssvalues and turns escaped message bodies and<br>tags into readable terminal text.
Consumers should branch on error.code, not on the free-form message text.
JSON stderr shape is stable:
{
"error": {
"code": "CONFIGURATION_ERROR",
"message": "Missing portal credentials. Email: LIBRUS_PORTAL_EMAIL is unset; fallback LIBRUS_EMAIL is unset. Password: LIBRUS_PORTAL_PASSWORD is unset; fallback LIBRUS_PASSWORD is unset.",
"details": {}
}
}Contract:
error.codeis the stable discriminator for machine handling.error.messageis human-facing and may be refined without changing the error category.error.detailsis optional and extensible. Callers should ignore unknown keys.- Existing documented codes stay stable.
- Future SDK features may add new codes without changing the overall JSON error envelope.
Current codes emitted by the SDK and CLI:
| Code | Meaning |
|---|---|
CONFIGURATION_ERROR |
Required credentials or other local configuration are missing or invalid. |
AUTHENTICATION_FAILED |
Portal or Gateway API 2.0 authentication failed. |
PORTAL_LOGIN_PAGE_INVALID |
The portal login page no longer matches the expected CSRF-token structure. |
NETWORK_TIMEOUT |
A portal or Synergia request exceeded the configured timeout. |
API_REQUEST_FAILED |
A portal or Synergia request failed with a non-maintenance HTTP error. |
SERVICE_MAINTENANCE |
The Synergia API reported maintenance mode. |
RESPONSE_VALIDATION_FAILED |
The live payload no longer matches the validated SDK schema. |
CHILD_NOT_FOUND |
No linked child account matched the provided selector. |
AMBIGUOUS_CHILD |
More than one linked child account matched the provided selector. |
CHILD_REQUIRED |
api_v3 needs --child, LIBRUS_CHILD, or an SDK child selector. |
UNSUPPORTED_BACKEND |
The selected API backend cannot support the requested operation. |
CLI_USAGE_ERROR |
The CLI arguments were invalid, incomplete, or used unsupported values. |
INTERNAL_ERROR |
Unexpected non-SDK error wrapper used by the CLI fallback path. |
Direct API 3.0 messages reads can fail with HTTP 403 even when other
child-scoped endpoints such as grades list still work. CLI
messages list, messages get, and messages unread use API 3.0 by default.
Pass --backend wiadomosci or use messages wiadomosci-list,
messages wiadomosci-get, and messages wiadomosci-unread to read through the
portal-authenticated wiadomosci.librus.pl inbox backend.
When a /Messages request returns API_REQUEST_FAILED with status: 403, the
SDK and CLI now add diagnostics under error.details:
feature: "messages"requiredScope: "messages"scopePresentwhenAuth/TokenInfoexposes scopes for the same child tokentokenScopeswhen those scopes are readablehintpointing tolibrus-sdk auth token-info --child <id-or-login>
Use the auth helper directly when you need to compare child tokens:
npm exec --package librus-sdk -- librus-sdk auth token-info --child <id-or-login>For timeout handling, the SDK throws LibrusNetworkTimeoutError, and the CLI
writes the same NETWORK_TIMEOUT failure to stderr. With --format json,
stderr includes secret-safe details shaped like { endpoint, timeoutMs }.
With --format text, stderr still includes the same stable code and the
human-facing timeout message. The endpoint is the request URL only;
credentials, bearer tokens, and cookie values are not included.
openapi.json is generated from the SDK's supported Synergia GET endpoints and
the shared valibot response schemas. The document is best-effort and
intentionally covers only the child-scoped api.librus.pl/3.0 requests, not
the portal.librus.pl login flow.
Regenerate or verify the file locally with:
npm run openapi:generate
npm run openapi:checkYou can also generate the document programmatically:
import { generateOpenApiDocument } from "librus-sdk";
const openApi = generateOpenApiDocument({ version: "0.4.0" });Repository release policy, SemVer rules, tag naming, changelog-backed release
notes, and badge-homepage guidance are documented in
docs/releasing.md.
npm install
npm run validate
npm run openapi:generate
npm run cli -- children list
npm run cli -- grades list --child <id-or-login>More examples:
npm run cli -- me --child <id-or-login>
npm run cli -- attendance list --child <id-or-login>
npm run cli -- homework list --child <id-or-login>
npm run cli -- lessons list --child <id-or-login>
npm run cli -- lucky-number get --child <id-or-login>
npm run cli -- messages list --child <id-or-login>
npm run cli -- timetable week --child <id-or-login> --week-start 2026-03-30
npm run cli -- announcements list --child <id-or-login>
npm run cli -- notes list --child <id-or-login>
npm run cli -- justifications conferences --child <id-or-login>
npm run cli -- auth photo --child <id-or-login> --id <photo-id> --output ./photo.jpgThe required pre-merge verification command for this repository is:
npm run validatenpm run validate runs the same canonical checks used by the CI validation
gate:
npm run lintnpm run format:checknpm run buildnpm run test:coveragenpm run pack:check
Behavior changes should add or update focused Vitest coverage in the closest
relevant suite under test/.
This repository's dynamic-analysis evidence is the automated Vitest test suite
plus coverage checks. That evidence comes from npm run test:coverage locally
and the Validation gate CI job in
./.github/workflows/ci.yml.
For Best Practices and repository-policy purposes, this project does not rely on fuzzing or browser scanning as its dynamic-analysis evidence.
Current CI jobs:
Validation gate: runs the requirednpm run validatepath before merge.Dependency vulnerability gate: fails on high or critical npm advisories.Snyk Security: fails on high or critical dependency vulnerabilities using the configuredSNYK_TOKENGitHub Actions secret.Trivy scan (informational): publishes vulnerability and secret-scan results for review.
This repo also includes optional live integration tests for the built SDK and
CLI artifacts in dist.
They are intended for local manual verification only:
- they require real Librus credentials
- they are manual-only and not required for ordinary merges
- they are not part of
npm test - they are not part of
npm run validate - they are not run in CI or release automation
Create a dedicated Portal env file at .env.integration.local:
LIBRUS_PORTAL_EMAIL=you@example.com
LIBRUS_PORTAL_PASSWORD=your-passwordThen run:
npm run test:integration
npm run test:integration:sdk
npm run test:integration:cli
npm run report:integration
npm run report:integration:sdk
npm run report:integration:cliFor Gateway API 2.0 verification, create
.env.integration.gateway-api-20.local with the school-issued login. The login
is printed or issued by the school; it is not an email address.
LIBRUS_GATEWAY_LOGIN=1234567
LIBRUS_GATEWAY_PASSWORD=your-passwordThen run:
npm run test:integration:gateway-api-20
npm run test:integration:gateway-api-20:sdk
npm run test:integration:gateway-api-20:cli
npm run report:integration:gateway-api-20
npm run report:integration:gateway-api-20:sdk
npm run report:integration:gateway-api-20:cliThe Gateway API 2.0 integration scripts force
LIBRUS_API_BACKEND=gateway_api_20, so a developer shell with Portal variables
set will not accidentally run the Portal matrix. The Gateway matrix is already
child-scoped: SDK checks use session.forChild() without a selector, and CLI
checks run commands such as librus-sdk grades list without --child.
Portal-only checks are skipped in the Gateway report because
gateway_api_20 cannot list Portal-linked children or create Portal-backed
BFF/wiadomosci clients.
To limit the Portal live run to specific children, set LIBRUS_TEST_CHILDREN as
a comma-separated list of child ids or logins:
LIBRUS_TEST_CHILDREN=7147345 npm run test:integration
LIBRUS_TEST_CHILDREN=7147345,child-login npm run report:integration