Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,8 @@ report*.json
globalConfig.json

# CourseLit files
domains_to_delete.txt
.codex
domains_to_delete.txt
.codex

# AI
.agents
18 changes: 10 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
## Development Tips

- Use `pnpm` as package manager.
- The project is structured as a monorepo i.e. a pnpm workspace. The apps are in `apps` folder and re-usable packages are in `packages`.
- The project is structured as a monorepo, i.e., a pnpm workspace. The apps are in the `apps` folder and reusable packages are in the `packages` folder.
- Command for running script in a workspace: `pnpm --filter <workspace> <command>`.
- Command for running tests: `pnpm test`.
- The project uses shadcn for building UI so stick to its conventions and design.
- The project uses shadcn for building UI, so stick to its conventions and design.
- In `apps/web` workspace, create a string first in `apps/web/config/strings.ts` and then import it in the `.tsx` files, instead of using inline strings.
- For admin/dashboard empty states in `apps/web`, prefer reusing `apps/web/components/admin/empty-state.tsx` instead of creating one-off placeholder UIs.
- When working with forms, always use refs to keep the current state of the form's data and use it to enable/disable the form submit button.
- Check the name field inside each package's package.json to confirm the right name—skip the top-level one.
- While working with forms, always use zod and react-hook-form to validate the form. Take reference implementation from `apps/web/components/admin/settings/sso/new.tsx`.
- `packages/scripts` is meant to contain maintenance scripts which can be re-used over and over, not one-off migrations. One-off migrations should be in `apps/web/.migrations`.
- `packages/utils` should be the place for containing utilities which are used in more than one package.
- `packages/utils` should contain utilities used in more than one package.
- `apps/web` and `apps/queue` can share business logic and db models. Common business logic should be moved to `packages/common-logic`. Common DB related functionality should be moved to `packages/orm-models`.
- For migrations (located in `apps/web/.migrations`), follow the "Gold Standard" pattern:
- Use **Cursors** (`.cursor()`) to stream data from MongoDB, ensuring the script remains memory-efficient regardless of dataset size.
- Use **Batching** with `bulkWrite` (e.g., batches of 500) to maximize performance and minimize network roundtrips.
- Ensure **Idempotency** (safe to re-run) by using upserts or `$setOnInsert` where applicable.
- When making changes to the structure of the Course, consider how it affects its representation on its public page (`apps/web/app/(with-contexts)/(with-layout)/p/[id]/page.tsx`) and the course viewer (`apps/web/app/(with-contexts)/course/[slug]/[id]/page.tsx`).
- `apps/web` is a multi-tenant app.
- Preserve the domain-owner invariant: `domain.email` identifies the school owner and public API keys resolve that owner as the API actor. Do not use raw `UserModel.update*`, `UserModel.delete*`, `DomainModel.update*`, migrations, or scripts in a way that changes/deletes the owner user, changes the owner user's permissions, or drifts `domain.email` away from the owner user without adding explicit guards and tests.
- Refrain from adding new GraphQL query/mutation unless required. If an existing query/mutation can be modified to implement the feature without making the query's/mutation's boundaries blurry, extend those.
- Always keep openapi.mjs files in sync with the actual implementation of the API endpoints.

### Workspace map (core modules):

Expand All @@ -42,17 +44,17 @@

## Documentation tips

- We manage product's documentation in `apps/docs`.
- When working on a new feature or changing an existing feature significantly, see if documenation should be updated.
- We manage the product's documentation in `apps/docs`.
- When working on a new feature or changing an existing feature significantly, see if documentation should be updated.
- No need to update documentation while doing bug fixes and refactors.
- If browser tool is available, see if you can automatically take revelant screenshots and include it in the documentation.
- If a browser tool is available, see if you can automatically take relevant screenshots and include them in the documentation.

## Testing instructions

- Always add or update test when introducing changes to `apps/web/graphql` folder, even if nobody asked.
- Always add or update tests when introducing changes to `apps/web/graphql` folder, even if nobody asked.
- Run `pnpm test` to run the tests.
- Fix any test or type errors until the whole suite is green.
- Refrain from creating new files when adding tests in `apps/web/graphql` subdirectories. Re-use `logic.test.ts` files for adding new test suites i.e. describe blocks.
- Refrain from creating new files when adding tests in `apps/web/graphql` subdirectories. Re-use `logic.test.ts` files for adding new test suites, i.e., describe blocks.

## PR instructions

Expand Down
8 changes: 8 additions & 0 deletions apps/docs-new/content/docs/developers/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ To interact with the CourseLit API, you need an API key. Follow these steps to o
2. Navigate to the dashboard.
3. Go to the `Settings > Miscellaneous > API Keys` section and generate a new API key.

## API Key Actor

API keys are school-level credentials. They are not attached to an individual user account.

When CourseLit receives an API-key-authenticated request, it resolves the school owner for the current domain and uses that owner as the actor for permission checks and resource ownership. For example, resources created through the API use the same owner-backed context that the dashboard business logic expects.

Do not send `userId`, `creatorId`, or similar ownership fields in API requests unless a specific endpoint documents that field as part of the customer being managed.

## Setting Up the Environment

Store your CourseLit server URL and API key securely in environment variables used by your application.
Expand Down
2 changes: 2 additions & 0 deletions apps/docs-new/content/docs/users/manage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Now when the user tries to generate a login link, they will get an error stating

Before changing user's permissions, read our [permissions](/users/permissions) guide so that you understand what you are doing.

The school owner's permissions cannot be changed. CourseLit uses the school owner as the actor for school-level operations such as public API requests, so the owner must always remain available with their existing permissions.

To change user's permissions:

1. Select on the user from the users list to open its editor.
Expand Down
8 changes: 8 additions & 0 deletions apps/docs/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ export const SIDEBAR: Sidebar = {
Developers: [
{ text: "Introduction", link: "en/developers/introduction" },
{ text: "Managing users", link: "en/developers/manage-users" },
{
text: "Products and content",
link: "en/developers/products-and-content",
},
{
text: "Customers and progress",
link: "en/developers/customers-and-progress",
},
],
"Self hosting": [
{ text: "Why self host?", link: "en/self-hosting/introduction" },
Expand Down
62 changes: 62 additions & 0 deletions apps/docs/src/pages/en/developers/customers-and-progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
title: Manage customers and progress using CourseLit API
description: Enroll customers and read product progress using the CourseLit API
layout: ../../../layouts/MainLayout.astro
---

The CourseLit public API can enroll customers into products and read enrollment/progress snapshots. These endpoints are useful for external CRMs, custom learning apps, and automation workflows that use CourseLit as the system of record.

For interactive schemas and examples, open the Swagger API reference from your CourseLit dashboard.

## Authentication

Send your API key in the `x-api-key` header.

```bash
curl https://your-school.example.com/api/products/product_123/customers \
-H "x-api-key: your-api-key" \
-H "accept: application/json"
```

API keys are school-level credentials. CourseLit resolves the school owner for the current domain and uses that owner as the actor for permission checks. Customer fields such as `userId`, memberships, and progress still refer to the customer being managed, not to the API key or school owner.

## Invite a customer

Use:

```http
POST /api/products/{productId}/customers/invitations
```

Request body:

```json
{
"email": "student@example.com",
"tags": ["cohort-2026"]
}
```

The endpoint uses CourseLit's existing customer invitation behavior. It creates or reuses a customer user, enrolls that customer into the product, and sends the invitation email.

## List product customers

Use:

```http
GET /api/products/{productId}/customers
```

The response is a paginated product roster. Supported query parameters include `page`, `limit`, `status`, and `search`.

## Read customer progress

Use:

```http
GET /api/products/{productId}/customers/{userId}/progress
```

Progress is read-only in the public API. The API reports existing CourseLit progress state such as completed lesson IDs, total published lessons, progress percentage, enrollment timestamps, and download state for download products.

The public API does not provide customer-runtime `/api/me` endpoints and does not let integrations arbitrarily set customer progress.
10 changes: 9 additions & 1 deletion apps/docs/src/pages/en/developers/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ To interact with the CourseLit API, you need an API key. Follow these steps to o
2. Navigate to the dashboard.
3. Go to the `Settings > Miscellaneous > API Keys` section and generate a new API key.

## API Key Actor

API keys are school-level credentials. They are not attached to an individual user account.

When CourseLit receives an API-key-authenticated request, it resolves the school owner for the current domain and uses that owner as the actor for permission checks and resource ownership. For example, resources created through the API use the same owner-backed context that the dashboard business logic expects.

Do not send `userId`, `creatorId`, or similar ownership fields in API requests unless a specific endpoint documents that field as part of the customer being managed.

## Setting Up the Environment

You need to set up your environment variables to store your CourseLit server URL and API key securely. Here is an example of how to do it in JavaScript:
Expand All @@ -42,10 +50,10 @@ export async function createUser({ email }) {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": courselitApikey,
},
body: JSON.stringify({
email,
apikey: courselitApikey,
}),
});

Expand Down
4 changes: 2 additions & 2 deletions apps/docs/src/pages/en/developers/manage-users.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ POST /api/user

- `Content-Type: application/json`
- `domain`: Your domain name
- `x-api-key`: Your API key

### Request Body

```json
{
"apikey": "your-api-key",
"email": "user@example.com",
"name": "User Name",
"permissions": ["read", "write"],
Expand Down Expand Up @@ -68,8 +68,8 @@ Here are the possible values for the `permissions` array:
curl -X POST https://yourdomain.com/api/user \
-H "Content-Type: application/json" \
-H "domain: yourdomain.com" \
-H "x-api-key: your-api-key" \
-d '{
"apikey": "your-api-key",
"email": "user@example.com",
"name": "User Name",
"permissions": ["course:manage", "community:manage"],
Expand Down
Loading
Loading