npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

brightspace-lms

v0.1.0

Published

API client wrapper to D2L's Brightspace REST API.

Readme

brightspace-lms

A community maintained TypeScript client for the D2L Brightspace Valence REST API, encapsulating authentication, version checking and negotiation, and resource modifications.

const me = await client.users.whoami();
const courses = await client.enrollments.listMyCourses();
const grades = await client.grades.listObjects(courseId);

Why this exists

This library exists to improve the usability of the D2L Valence API, by encapsulating the docs and the internal wiring in one place, so developers can call simple, typed methods in their apps while the client handles the underlying API mechanics.

It was originally built to power Vita Learning, an AI-native study platform for university students. The library is used in production and maintained accordingly.


Installation

npm install brightspace-lms
# or
pnpm add brightspace-lms

Requirements: Node.js 18+ (uses native fetch and crypto.randomUUID)


Quick start

import { BrightspaceClient, BearerTokenClient } from "brightspace-lms";

const client = new BrightspaceClient({
  host: "https://your-org.brightspace.com",
  auth: new BearerTokenClient({ token: process.env.BRIGHTSPACE_TOKEN! }),
});

const me = await client.users.whoami();
console.log(`Hello, ${me.FirstName}`);

Authentication

Bearer token

Use when you already have a valid access token — the simplest option for server-side scripts.

auth: new BearerTokenClient({ token: process.env.BRIGHTSPACE_TOKEN! })

OAuth2 — Authorization Code Grant

For user-delegated access. Use OAuth2AuthorizationCodeClient — construct one instance per request. Do not share a single instance across users; credentials are stored on the instance and are user-specific.

Returning session — you have a refreshToken stored from a previous session:

import { OAuth2AuthorizationCodeClient, BrightspaceClient } from "brightspace-lms";

const auth = new OAuth2AuthorizationCodeClient({
  clientId: process.env.D2L_CLIENT_ID!,
  clientSecret: process.env.D2L_CLIENT_SECRET!,
  redirectUri: "https://app.example.com/oauth/callback",
  scope: "core:*:*",
});

auth.setCredentials({ refreshToken: session.refreshToken });

const client = new BrightspaceClient({ host: process.env.D2L_HOST!, auth });
const me = await client.users.whoami();

Fresh auth — the user hasn't authorized yet:

import { OAuth2AuthorizationCodeClient, BrightspaceClient } from "brightspace-lms";

const authConfig = {
  clientId: process.env.D2L_CLIENT_ID!,
  clientSecret: process.env.D2L_CLIENT_SECRET!,
  redirectUri: "https://app.example.com/oauth/callback",
  scope: "core:*:*",
};

// Login route — generate the redirect URL
const auth = new OAuth2AuthorizationCodeClient(authConfig);
const state = crypto.randomUUID();
session.oauthState = state;
res.redirect(auth.generateAuthUrl(state));

// Callback route — exchange the code
const auth = new OAuth2AuthorizationCodeClient(authConfig);
if (req.query.state !== session.oauthState) throw new Error("CSRF mismatch");
const { tokens } = await auth.getToken(req.query.code);
session.refreshToken = tokens.refreshToken; // persist for future requests

const client = new BrightspaceClient({ host: process.env.D2L_HOST!, auth });
const me = await client.users.whoami();

The shared thing across requests is the static config object — not the OAuth2AuthorizationCodeClient instance. Each request constructs a fresh instance and hydrates it with the user's stored refreshToken.

OAuth2 — Client Credentials Grant

For server-to-server integrations. Use OAuth2ClientCredentialsClient — safe to construct once at module level and share across requests, since all credentials are static configuration.

D2L's client credentials flow uses JWT client assertions signed with a private key (not a client secret). Your JWKS URL must be publicly reachable over HTTPS.

import { OAuth2ClientCredentialsClient, BrightspaceClient } from "brightspace-lms";

// Module level — safe to share across requests
const auth = new OAuth2ClientCredentialsClient({
  clientId: process.env.D2L_CLIENT_ID!,
  privateKey: process.env.D2L_PRIVATE_KEY_PEM!,
  keyId: process.env.D2L_KEY_ID!,
  algorithm: "RS256",   // RS256 | RS384 | RS512 | ES256 | ES384 | ES512
  scope: "core:*:*",
});

const client = new BrightspaceClient({ host: process.env.D2L_HOST!, auth });

See D2L's OAuth2 documentation for application registration steps and JWKS requirements.

Legacy ID-Key

Supported for compatibility. D2L deprecated this auth system in January 2023 — migrate to OAuth2 where possible.

auth: {
  type: "legacy",
  appId: "<app-id>",
  appKey: "<app-key>",
  userId: "<user-id>",
  userKey: "<user-key>",
}

Version negotiation

Brightspace versions each API component independently (lp, le, etc.) via URL segments. On the first resource call, the client posts to POST /d2l/api/versions/check to confirm the host supports the required versions. Concurrent first calls are deduplicated into a single negotiation request.

// Trigger negotiation explicitly
const result = await client.versions.check();
console.log(result.Supported); // false if the host is too old

// Pin specific versions if needed
const client = new BrightspaceClient({
  host: "https://your-org.brightspace.com",
  auth: { ... },
  apiVersions: {
    lp: "1.49",
    le: "1.82",
  },
});

If the host cannot satisfy required versions, a VersionError is thrown on the first call and all subsequent calls short-circuit immediately without retrying.

The library targets the oldest fully supported contract versions per D2L's deprecation table. Deprecated and obsolete contracts are not used. See COMPATIBILITY.md for the version matrix.


Resources

| Namespace | D2L product | Coverage | |---|---|---| | client.users | lp | whoami, retrieve, list, create, update, del, activation, names, pronouns, password | | client.enrollments | lp | listMyCourses, listOrgUnitUsers, listUserEnrollments, retrieveClasslist, create, del, completion | | client.orgUnits | lp | retrieve, list, create, update, del, ancestors, descendants, parents, children, types, recycle bin | | client.courses | lp | retrieve, list, create, update, del, templates, bulk updater | | client.grades | le | objects, values, categories, schemes | | client.quizzes | le | retrieve, list, create, update, del, attempts, categories | | client.dropboxes | le | folders, submissions, feedback, categories | | client.assessments | le | rubrics, assessments (LE API v1.93+) | | client.calendar | le | events, occurrences, presenters | | client.discussions | le | forums, topics, posts, statistics | | client.content | le | modules, topics, table of contents, user progress | | client.groups | lp | group categories, groups, enrollments, sections | | client.surveys | le | surveys, attempts, categories, special access | | client.news | le / lp | news items, user feed, sharing rules | | client.checklists | le | checklists, categories, items | | client.outcomes | le | org-level sets, org-unit sets, alignments, import/export | | client.awards | bas | library, associations, issued awards | | client.releaseConditions | le | conditions for content, discussions, quizzes, dropboxes | | client.demographics | lp | fields, data types, user entries | | client.accommodations | le | quizzing accommodations per user and org unit | | client.versions | — | version check and negotiation |

// Users
const me = await client.users.whoami();
const user = await client.users.retrieve(12345);
const page = await client.users.list({ isActive: true });

// Enrollments
const courses = await client.enrollments.listMyCourses({ orgUnitTypeId: 3 });
const classlist = await client.enrollments.retrieveClasslist(orgUnitId);
await client.enrollments.create({ OrgUnitId: 9999, UserId: 12345, RoleId: 111 });

// Courses
const course = await client.courses.retrieve(9999);
await client.courses.update(9999, { Name: "Updated Name", ... });

// Grades
const objects = await client.grades.listObjects(9999);
await client.grades.updateValue(9999, gradeObjectId, userId, {
  Comments: { Content: "Well done", Type: "Text" },
  PointsNumerator: 92,
});

Pagination

All list endpoints return a PaginatedList<T> backed by D2L's bookmark cursor. The cursor is handled internally — consumers never see Bookmark or HasMoreItems.

const page = await client.users.list({ isActive: true });

// Iterate all pages
for await (const user of page) {
  process(user);
}

// Collect everything
const all = await page.toArray();

// Manual page control
if (page.hasMore) {
  const next = await page.nextPage();
}

Middleware

Register middleware to observe or transform requests, responses, and errors. Useful for logging, metrics, and custom headers.

client.use({
  onRequest(request) {
    request.headers.set("X-Correlation-Id", generateId());
    return request;
  },
  onResponse(ctx) {
    logger.info(ctx.request.method, ctx.request.path, ctx.response.status);
    return ctx;
  },
  onError(error) {
    metrics.increment("brightspace.error");
    throw error;
  },
});

Multiple middleware can be registered and are applied in registration order.


Custom endpoints

For endpoints not yet covered by a named resource, use extend() for a typed helper or raw for direct HTTP access. Both retain auth and middleware handling.

// Typed helper
const custom = client.extend<{ Value: number }>("/d2l/api/le/1.82/123/custom");
const result = await custom.get();

// Direct HTTP
const result = await client.raw.get<{ Items: unknown[] }>(
  "/d2l/api/lp/1.49/some/endpoint"
);

Error handling

All errors thrown by this library extend BrightspaceError. D2L's Errors.ProblemDetails response bodies are parsed and surfaced in the error message automatically.

| Class | Trigger | |---|---| | AuthError | 401 / 403 — missing, expired, or insufficient-scope token | | ValidationError | 400 — malformed request body or missing required fields | | NotFoundError | 404 — resource not found, or obsolete API contract | | RateLimitError | 429 — credit bucket exhausted; carries retryAfter, creditsRemaining, requestCost | | ServerError | 5xx — D2L server-side error | | VersionError | Host does not support the required API component version | | NetworkError | Timeout, connection refused, or DNS failure |

import {
  isRateLimitError,
  isVersionError,
  BrightspaceError,
} from "brightspace-lms";

try {
  await client.users.whoami();
} catch (error) {
  if (isRateLimitError(error)) {
    console.warn(`Rate limited. Retry in ${error.retryAfter}s.`);
    return;
  }
  if (isVersionError(error)) {
    console.error(`Host too old: ${error.product} needs ${error.required}, has ${error.available}`);
    return;
  }
  if (error instanceof BrightspaceError) {
    console.error(error.status, error.message, error.rawBody);
    return;
  }
  throw error;
}

The client automatically retries 5xx and 429 responses with exponential backoff (default: 3 retries). Configure via retries and timeout in the client config.


Compatibility

| brightspace-lms | D2L lp | D2L le | Oldest supported LMS | |---|---|---|---| | 1.x | ≥ 1.49 | ≥ 1.82 | 20.25.x |

See COMPATIBILITY.md for the full version matrix and deprecation tracking.


Contributing

See CONTRIBUTING.md.


API Coverage

Coverage of the D2L Brightspace Valence API. Resources marked out of scope are accessible via client.raw or client.extend().

Implemented

| Resource | Namespace | D2L docs | |---|---|---| | Users | client.users | res/user.html | | Enrollments | client.enrollments | res/enroll.html | | Org units & structure | client.orgUnits | res/orgunit.html | | Course offerings & templates | client.courses | res/course.html | | Grades & schemes | client.grades | res/grade.html | | Quizzes & attempts | client.quizzes | res/quiz.html | | Dropboxes & submissions | client.dropboxes | res/dropbox.html | | Assessments & rubrics | client.assessments | res/assessment.html | | Calendar & events | client.calendar | res/calendar.html | | Discussions & forums | client.discussions | res/discuss.html | | Course content | client.content | res/content.html | | Groups & sections | client.groups | res/groups.html | | Surveys | client.surveys | res/survey.html | | News & announcements | client.news | res/news.html | | Checklists | client.checklists | res/checklist.html | | Learning Outcomes | client.outcomes | res/outcomes.html | | Awards & badges | client.awards | res/awards.html | | Release conditions | client.releaseConditions | res/releaseconditions.html | | Demographics | client.demographics | res/demographics.html | | Accommodations | client.accommodations | res/accommodations.html | | Version negotiation | client.versions | basic/version.html |



Out of scope

These resource groups are either highly institution-specific, require elevated admin access, or cover niche use cases unlikely to benefit from a typed SDK wrapper. They remain accessible via client.raw or client.extend().

| Resource | D2L docs | Reason | |---|---|---| | ePortfolio | res/epobject.html | Large surface, niche use, separate ep product component | | LTI Advantage assets | res/ltiadvantage.html | LTI tooling is a separate integration concern | | LTI legacy assets | res/lti.html | Deprecated protocol | | Data Hub / Data Export | res/dataExport.html | Bulk export, not a request/response API | | IPSIS SIS integration | res/ipsis.html | SIS-specific, institution-level admin only | | Tools management | res/tools.html | Platform admin surface, not course-level | | Configuration variables | res/config.html | Platform admin surface | | Permissions | res/permissions.html | Role permission management, admin-only | | Learning repository | res/lor.html | Separate lr product, limited adoption | | Locales & time zones | res/locale.html | Static reference data, not a resource API |


License

MIT — © Richard Antao