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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- 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.

### Workspace map (core modules):
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
2 changes: 1 addition & 1 deletion apps/docs-new/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
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
139 changes: 139 additions & 0 deletions apps/docs/src/pages/en/developers/products-and-content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
title: Manage products and content using CourseLit API
description: Create products, payment plans, sections, lessons, and media-backed content using the CourseLit API
layout: ../../../layouts/MainLayout.astro
---

The CourseLit public API can manage products and course content programmatically. Use it when you are building another frontend for CourseLit, such as an AI-assisted learning app that creates courses, uploads media, and publishes products through CourseLit.

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 \
-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 and resource ownership. Do not send `creatorId`, `userId`, or similar ownership fields unless an endpoint explicitly documents the field as the customer being managed.

## Product workflow

Create a product as a draft first.

```http
POST /api/products
```

The draft creation endpoint accepts only:

```json
{
"title": "AI Foundations",
"type": "course"
}
```

After the draft exists, update metadata with:

```http
PATCH /api/products/{productId}
```

Use this endpoint for fields such as `slug`, `description`, `published`, `privacy`, `tags`, and `featuredImage`. `description` is a JSON-stringified Tiptap/ProseMirror document.

Do not use the legacy `course.cost` or `course.costType` fields. Pricing is managed with payment plans.

## Payment plans

Course and download products require a payment plan before publishing. Use:

```http
GET /api/products/{productId}/payment-plans
POST /api/products/{productId}/payment-plans
GET /api/products/{productId}/payment-plans/{planId}
PATCH /api/products/{productId}/payment-plans/{planId}
DELETE /api/products/{productId}/payment-plans/{planId}
POST /api/products/{productId}/payment-plans/{planId}/default
```

Deleting a payment plan archives it. The API follows the same validations as the dashboard, including checks for paid plans and default payment plans.

## Sections and lessons

Structured products can be organized with sections and lessons.

```http
GET /api/products/{productId}/sections
POST /api/products/{productId}/sections
PATCH /api/products/{productId}/sections/{sectionId}
DELETE /api/products/{productId}/sections/{sectionId}
POST /api/products/{productId}/sections/reorder
GET /api/products/{productId}/lessons
POST /api/products/{productId}/lessons
GET /api/products/{productId}/lessons/{lessonId}
PATCH /api/products/{productId}/lessons/{lessonId}
DELETE /api/products/{productId}/lessons/{lessonId}
POST /api/products/{productId}/lessons/{lessonId}/move
```

Text lessons accept Tiptap/ProseMirror JSON in `content`.

```json
{
"title": "Welcome",
"type": "text",
"content": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Welcome to the course." }
]
}
]
}
}
```

SCORM lesson creation is not supported by the public API. If you send a SCORM lesson type, the API returns a `not_supported` error.

## Media-backed lessons

Upload files directly to MediaLit, then reference the returned `mediaId` when creating or updating video, audio, PDF, or file lessons.

1. Generate an upload signature:

```http
POST /api/media/presigned
```

2. Upload the file to MediaLit using the returned `signature` and `endpoint`.

See the MediaLit upload guide: <a href="https://docs.medialit.cloud/api/uploadMedia" target="_blank">https://docs.medialit.cloud/api/uploadMedia</a>.

3. Reference the uploaded media in a lesson:

```json
{
"title": "Lecture video",
"type": "video",
"media": {
"mediaId": "media_123"
}
}
```

## Publishing

Publish with:

```http
PATCH /api/products/{productId}
```

Set `published` to `true` only after the product is ready and has the required payment plan setup. The API uses existing CourseLit publishing validation.
2 changes: 2 additions & 0 deletions apps/docs/src/pages/en/users/manage.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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](/en/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
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
TOAST_MAIL_SENT,
TOAST_TITLE_SUCCESS,
} from "@ui-config/strings";
import { Email as EmailContent } from "@courselit/email-editor";
import type { Email as EmailContent } from "@courselit/email-editor";
import { useSequence } from "@/hooks/use-sequence";
import { useGraphQLFetch } from "@/hooks/use-graphql-fetch";
import FilterContainer from "@components/admin/users/filter-container";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
use,
startTransition,
} from "react";
import { Email as EmailContent } from "@courselit/email-editor";
import type { Email as EmailContent } from "@courselit/email-editor";
import { useGraphQLFetch } from "@/hooks/use-graphql-fetch";
import { Email } from "@courselit/common-models";
import EmailViewer from "@components/admin/mails/email-viewer";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ import { Form, useToast } from "@courselit/components-library";
import { FetchBuilder } from "@courselit/utils";
import Resources from "@components/resources";
import EmailViewer from "@components/admin/mails/email-viewer";
import { defaultEmail, Email as EmailContent } from "@courselit/email-editor";
import { defaultEmail } from "@courselit/email-editor";
import type { Email as EmailContent } from "@courselit/email-editor";
import constants from "@/config/constants";

const { permissions } = UIConstants;
Expand Down
Loading
Loading