Courses

Available. Course management shipped in LMS-571: list/create/get/update/archive courses, list/add modules, and per-course enrollments. The Add Module endpoint covers content module types (video, audio, document, iframe, youtube_vimeo, quiz, html, archive); SCORM/cmi5/AICC modules are added by uploading a package through the admin UI. Writes require the write scope (archive requires admin).

The Courses API lets you manage your course catalog — create courses, maintain modules, and retrieve enrollment lists per course.


The Course Object

{
  "id": "e7f6a5b4-8c9d-0123-efab-234567890123",
  "title": "Workplace Safety Fundamentals",
  "description": "An introduction to workplace health and safety regulations in Canada.",
  "short_description": "Workplace health and safety basics for Canadian workplaces.",
  "status": "published",
  "category_id": "1a2b3c4d-5e6f-7890-abcd-456789012345",
  "estimated_minutes": 45,
  "current_version_id": "5e6f7a8b-9c0d-1234-efab-567890123456",
  "published_at": "2026-01-12T15:00:00Z",
  "created_at": "2026-01-10T09:00:00Z",
  "updated_at": "2026-05-20T14:30:00Z",
  "enrollment_count": 14
}

Course status values:

StatusDescription
draftNot visible to learners
publishedAvailable for enrollment
archivedSoft-deleted; hidden from catalog; history preserved

Endpoints

List Courses

GET /api/v1/courses

Returns a paginated list of courses in the tenant’s catalog.

Authentication required: read scope

Query Parameters

ParameterTypeRequiredDescription
pageintegerNoPage number. Default: 1
per_pageintegerNoDefault: 50, max: 200
statusstringNoFilter: draft, published, archived. Default: excludes archived
categorystringNoFilter by category name (exact match)
searchstringNoCase-insensitive search on title and description
created_afterISO 8601NoFilter courses created after this timestamp
sort_bystringNocreated_at, title, updated_at. Default: created_at
sort_orderstringNoasc or desc. Default: desc

Example Request

curl "https://{your-subdomain}.embaylms.com/api/v1/courses?status=published&category=Compliance&per_page=20" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000"

Example Response

{
  "data": [
    {
      "id": "e7f6a5b4-8c9d-0123-efab-234567890123",
      "title": "Workplace Safety Fundamentals",
      "short_description": "Workplace health and safety basics for Canadian workplaces.",
      "status": "published",
      "category_id": "1a2b3c4d-5e6f-7890-abcd-456789012345",
      "estimated_minutes": 45,
      "published_at": "2026-01-12T15:00:00Z",
      "created_at": "2026-01-10T09:00:00Z",
      "updated_at": "2026-05-20T14:30:00Z",
      "enrollment_count": 14
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 87
  },
  "error": null
}

Create Course

POST /api/v1/courses

Creates a new course in draft status.

Authentication required: write scope

Request Body

FieldTypeRequiredDescription
titlestringYesCourse title
descriptionstringNoCourse description (plain text or Markdown)
short_descriptionstringNoShort catalog blurb (max 500 characters)
estimated_minutesintegerNoEstimated completion time in minutes

An initial draft version is created alongside the course, so modules can be added right away.

Example Request

curl -X POST https://{your-subdomain}.embaylms.com/api/v1/courses \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "WHMIS 2015",
    "description": "Workplace Hazardous Materials Information System refresher course.",
    "short_description": "WHMIS refresher.",
    "estimated_minutes": 30
  }'

Example Response

{
  "data": {
    "id": "2b3c4d5e-6f7a-8901-bcde-901234567890",
    "title": "WHMIS 2015",
    "description": "Workplace Hazardous Materials Information System refresher course.",
    "short_description": "WHMIS refresher.",
    "status": "draft",
    "category_id": null,
    "estimated_minutes": 30,
    "created_at": "2026-06-05T10:15:00Z",
    "updated_at": "2026-06-05T10:15:00Z"
  },
  "meta": null,
  "error": null
}

HTTP Status: 201 Created


Get Course

GET /api/v1/courses/{id}

Returns a single course by UUID.

Authentication required: read scope

Path Parameters

ParameterTypeRequiredDescription
idUUIDYesCourse UUID

Example Request

curl "https://{your-subdomain}.embaylms.com/api/v1/courses/e7f6a5b4-8c9d-0123-efab-234567890123" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000"

Example Response

{
  "data": {
    "id": "e7f6a5b4-8c9d-0123-efab-234567890123",
    "title": "Workplace Safety Fundamentals",
    "description": "An introduction to workplace health and safety regulations in Canada.",
    "short_description": "Workplace health and safety basics for Canadian workplaces.",
    "status": "published",
    "category_id": "1a2b3c4d-5e6f-7890-abcd-456789012345",
    "estimated_minutes": 45,
    "current_version_id": "5e6f7a8b-9c0d-1234-efab-567890123456",
    "published_at": "2026-01-12T15:00:00Z",
    "created_at": "2026-01-10T09:00:00Z",
    "updated_at": "2026-05-20T14:30:00Z",
    "enrollment_count": 14
  },
  "meta": null,
  "error": null
}

Common Errors

HTTP StatusDescription
404No course with this ID in the tenant

Update Course

PATCH /api/v1/courses/{id}

Updates one or more fields on a course. Supports publishing a draft (status: "published"), unpublishing back to draft, or archiving (status: "archived", equivalent to DELETE).

Authentication required: write scope

Path Parameters

ParameterTypeRequiredDescription
idUUIDYesCourse UUID

Request Body

All fields are optional.

FieldTypeDescription
titlestringCourse title
descriptionstringCourse description
short_descriptionstringShort catalog blurb (max 500 characters)
statusstringdraft, published, or archived. "archived" is equivalent to the DELETE endpoint below

Example Request

curl -X PATCH "https://{your-subdomain}.embaylms.com/api/v1/courses/2b3c4d5e-6f7a-8901-bcde-901234567890" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000" \
  -H "Content-Type: application/json" \
  -d '{ "status": "published" }'

Example Response

Returns the updated course object (same structure as Get Course).

Common Errors

HTTP StatusDescription
400Invalid field, or no updatable fields provided
404Course not found
422The course has no version to publish, or publishing would exceed your plan’s active-course cap

Plan limits

Publishing a course activates it. If your plan carries an active-course cap and you are already at it, the request is rejected with 422 and the error object carries a machine-readable entitlement_blocked payload:

{
  "data": null,
  "meta": null,
  "error": {
    "code": 422,
    "message": "The free plan allows 3 active courses. Upgrade to publish more.",
    "entitlement_blocked": {
      "code": "ENTITLEMENT_BLOCKED",
      "flag_or_limit": "course_cap",
      "current_tier": "free",
      "required_tier": "starter"
    }
  }
}

Only a real activation is gated — re-publishing a course that is already published, updating other fields, and archiving are never blocked. Paid plans have unlimited active courses.


Archive Course

DELETE /api/v1/courses/{id}

Archives a course. This is a soft delete — the course is hidden from the catalog and no new enrollments can be created, but existing learner history and completions are preserved.

Authentication required: admin scope

Path Parameters

ParameterTypeRequiredDescription
idUUIDYesCourse UUID

Example Request

curl -X DELETE "https://{your-subdomain}.embaylms.com/api/v1/courses/e7f6a5b4-8c9d-0123-efab-234567890123" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000"

HTTP Status: 204 No Content


List Modules

GET /api/v1/courses/{id}/modules

Returns all modules for a course in display order.

Authentication required: read scope

Path Parameters

ParameterTypeRequiredDescription
idUUIDYesCourse UUID

Example Request

curl "https://{your-subdomain}.embaylms.com/api/v1/courses/e7f6a5b4-8c9d-0123-efab-234567890123/modules" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000"

Example Response

{
  "data": [
    {
      "id": "3c4d5e6f-7a8b-9012-cdef-012345678901",
      "title": "Introduction to WHMIS",
      "description": "Overview of hazard classes and labels.",
      "module_type": "video",
      "is_required": true,
      "order_index": 0,
      "created_at": "2026-01-10T09:00:00Z"
    },
    {
      "id": "4d5e6f7a-8b9c-0123-defa-123456789012",
      "title": "Final Assessment",
      "description": null,
      "module_type": "quiz",
      "is_required": true,
      "order_index": 1,
      "created_at": "2026-01-10T09:05:00Z"
    }
  ],
  "meta": null,
  "error": null
}

Add Module

POST /api/v1/courses/{id}/modules

Adds a new module to a course. The module is appended at the end of the module list by default.

Package and asset upload (…/modules/{moduleId}/assets) is an in-app, session-authenticated route and is not part of the public API; create the module here, then upload its content from the course editor.

Authentication required: write scope

Path Parameters

ParameterTypeRequiredDescription
idUUIDYesCourse UUID

Request Body

FieldTypeRequiredDescription
titlestringYesModule title
module_typestringYesvideo, audio, document, iframe, youtube_vimeo, quiz, html, or archive. An archive module is a downloadable ZIP delivered as-is via a pre-signed URL (allow_download forced on); it completes on first download (LMS-193). SCORM/cmi5/AICC modules are added by uploading a package through the admin UI
descriptionstringNoModule description (max 2000 characters)
is_requiredbooleanNoWhether completion is required for course completion. Default: true

Example Request

curl -X POST "https://{your-subdomain}.embaylms.com/api/v1/courses/e7f6a5b4-8c9d-0123-efab-234567890123/modules" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Video: Hazardous Material Handling",
    "module_type": "video",
    "is_required": true
  }'

Example Response

{
  "data": {
    "id": "5e6f7a8b-9c0d-1234-efab-234567890123",
    "title": "Video: Hazardous Material Handling",
    "description": null,
    "module_type": "video",
    "is_required": true,
    "order_index": 2,
    "created_at": "2026-06-05T10:20:00Z"
  },
  "meta": null,
  "error": null
}

HTTP Status: 201 Created


List Course Enrollments

GET /api/v1/courses/{id}/enrollments

Returns all enrollments for a specific course.

Authentication required: read scope

Path Parameters

ParameterTypeRequiredDescription
idUUIDYesCourse UUID

Query Parameters

ParameterTypeRequiredDescription
statusstringNoFilter: active, completed, dropped, expired, waitlisted, pending_approval
pageintegerNoDefault: 1
per_pageintegerNoDefault: 50, max: 200

Example Request

curl "https://{your-subdomain}.embaylms.com/api/v1/courses/e7f6a5b4-8c9d-0123-efab-234567890123/enrollments?status=active" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000"

Example Response

{
  "data": [
    {
      "id": "d6e5f4a3-7b8c-9012-defa-123456789012",
      "user_id": "a3f2c1d0-4e5b-6789-abcd-ef0123456789",
      "course_id": "e7f6a5b4-8c9d-0123-efab-234567890123",
      "status": "active",
      "due_date": "2026-05-01T00:00:00Z",
      "completed_at": null,
      "source": "api",
      "created_at": "2026-04-01T08:00:00Z"
    }
  ],
  "meta": { "page": 1, "per_page": 50, "total": 14 },
  "error": null
}

Enroll a User in a Course

POST /api/v1/courses/{id}/enrollments

Enrolls a user in this course through the same enrollment engine the application uses — prerequisite, seat-cap, enrollment-window and duplicate guards all apply.

Authentication required: write scope

Path Parameters

ParameterTypeRequiredDescription
idUUIDYesCourse UUID

Request Body

FieldTypeRequiredDescription
user_idUUIDYesThe user to enroll
due_dateISO 8601 datetimeNoOptional due date for the assignment

Example Request

curl -X POST "https://{your-subdomain}.embaylms.com/api/v1/courses/e7f6a5b4-8c9d-0123-efab-234567890123/enrollments" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000" \
  -H "Content-Type: application/json" \
  -d '{ "user_id": "a3f2c1d0-4e5b-6789-abcd-ef0123456789" }'

Responses

StatusMeaning
201Enrollment created
400Invalid field (e.g. malformed user_id)
404Course or user not found
409Already enrolled

See also POST /api/v1/enrollments for the course-agnostic form of the same operation, and POST /api/v1/enrollments/bulk for bulk enrollment.