mobieusKnow mobieusCore API — mobieusLearn History #92
Author
system
Submitted
Jun 2, 2026 10:32am
Summary
toolkit seed-community initial
# mobieusCore API — mobieusLearn
The Learn surface of the public REST API. Manage courses, modules,
lessons, activities, enrollments, attempts, certificates,
certificate templates, and SCORM packages.
Added in API version **1.3.0** (2026-06-02).
## Scopes
| Scope | Grants |
|---|---|
| `learn:read` | List + read every Learn resource (courses, enrollments, attempts, certificates, templates, SCORM packages, nested entities). |
| `learn:write` | Create / update / publish / archive courses, enroll users, cancel enrollments, revoke + regenerate certificates. |
| `learn:cohorts:grant` | Grant cohort access via `POST /api/v1/learn/cohorts/grant`. |
Generate a key at **`/admin/api-keys`**, tick the scopes you need.
Pro / Creator Plus / Sovereign plans only — same plan gate as the
rest of the public API.
## Quick start
```bash
curl https://YOUR-TENANT.mobieus.io/api/v1/learn/courses \
-H 'Authorization: Bearer mc_live_...'
```
Returns up to 50 courses ordered by id desc. Use `?cursor=...&limit=N`
(max 100) to page; `next_cursor` is null on the last page.
## Resources
### Courses
| Method | Path | Scope | What |
|---|---|---|---|
| `GET` | `/api/v1/learn/courses` | `learn:read` | List courses (cursor-paginated). |
| `GET` | `/api/v1/learn/courses/{id}` | `learn:read` | Get a single course (with credit info, version status, owner author). |
| `POST` | `/api/v1/learn/courses` | `learn:write` | Create a course. Required: `slug`, `title`. Optional: subtitle, summary, language, completion_rule, estimated_minutes, credit_hours, credit_kind, credit_expires_after_months. |
| `POST` | `/api/v1/learn/courses/{id}` | `learn:write` | Update. Any field from create is acceptable; omitted fields keep their value. |
| `POST` | `/api/v1/learn/courses/{id}/publish` | `learn:write` | Move to published. |
| `POST` | `/api/v1/learn/courses/{id}/unpublish` | `learn:write` | Revert to draft. |
| `POST` | `/api/v1/learn/courses/{id}/archive` | `learn:write` | Archive (hides from learners; preserves enrollments). |
**Example: enroll-on-purchase flow**
```bash
# 1. Create a course
curl -X POST https://YOUR-TENANT.mobieus.io/api/v1/learn/courses \
-H 'Authorization: Bearer mc_live_...' -H 'Content-Type: application/json' \
-d '{"slug":"intro-to-mobieus","title":"Intro to Mobieus","completion_rule":"all_required","credit_hours":2.5,"credit_kind":"CEU"}'
# 2. After a Stripe checkout completes, enroll the buyer
curl -X POST https://YOUR-TENANT.mobieus.io/api/v1/learn/enrollments \
-H 'Authorization: Bearer mc_live_...' -H 'Content-Type: application/json' \
-d '{"user_id":123,"course_id":42,"source":"stripe_purchase"}'
```
### Nested entities (read-only)
| `GET` | `/api/v1/learn/courses/{id}/modules` | `learn:read` | Modules in a course. |
| `GET` | `/api/v1/learn/modules/{mid}/lessons` | `learn:read` | Lessons in a module. |
| `GET` | `/api/v1/learn/lessons/{lid}/activities` | `learn:read` | Activities in a lesson. |
Writes for modules / lessons / activities go through the admin UI;
+ they enforce per-course team membership (see M9-2 in the changelog).
+ Per-course capability scoping for the API key model lands in a
+ future minor version.
they enforce per-course team membership. Per-course capability
scoping for the API key model lands in a future minor version.
### Enrollments
| `GET` | `/api/v1/learn/enrollments?course_id=&user_id=` | `learn:read` | List, optionally filtered. |
| `GET` | `/api/v1/learn/enrollments/{id}` | `learn:read` | Get one. |
| `POST` | `/api/v1/learn/enrollments` | `learn:write` | Enroll a user. Body: `{"user_id", "course_id", "source"?, "expires_at"?}`. |
| `POST` | `/api/v1/learn/enrollments/{id}/cancel` | `learn:write` | Cancel (status flips to `cancelled`). |
### Cohorts
| `POST` | `/api/v1/learn/cohorts/grant` | `learn:cohorts:grant` | Grant a user access to a cohort. |
### Attempts
| `GET` | `/api/v1/learn/attempts?enrollment_id=&activity_id=` | `learn:read` | List. |
| `GET` | `/api/v1/learn/attempts/{id}` | `learn:read` | Get one (includes score_raw, score_scaled, passed, status, time_started_at, time_ended_at, total_time_seconds). |
### Certificates
| `GET` | `/api/v1/learn/certificates?user_id=&course_id=` | `learn:read` | List. |
| `GET` | `/api/v1/learn/certificates/{id}` | `learn:read` | Get one. |
| `GET` | `/api/v1/learn/certificates/verify/{vid}` | `learn:read` | Look up by the 40-char verification id. Returns `is_revoked: true|false`. |
| `POST` | `/api/v1/learn/certificates/{id}/revoke` | `learn:write` | Revoke. Body: `{"reason": "..."}`. Required. |
| `POST` | `/api/v1/learn/certificates/{id}/regenerate-pdf` | `learn:write` | Rebuild the PDF (after editing a template). Verification URL unchanged. |
### Certificate templates
| `GET` | `/api/v1/learn/cert-templates` | `learn:read` | List templates (per-tenant brand identity for certs). |
| `GET` | `/api/v1/learn/cert-templates/{id}` | `learn:read` | Get one. |
### SCORM packages
| `GET` | `/api/v1/learn/scorm-packages` | `learn:read` | List packages (any status). |
| `GET` | `/api/v1/learn/scorm-packages/{id}` | `learn:read` | Get one with its SCO list. |
Importing a SCORM package via API isn't supported in 1.3.0 — use the
admin UI at `/admin/learn/scorm/new`. The endpoint is on the roadmap.
## Response envelope
Every endpoint returns:
```json
{
"data": ...,
"next_cursor": "...",
"request_id": "req_..."
}
```
`next_cursor` is null when there's no more data.
## Errors
Errors use the standard v1 envelope — a single `error` object with a
`code`, a `message`, and the `request_id`:
```json
{
+ "error": "course_not_found",
+ "message": "Course not found.",
+ "request_id": "req_..."
"error": {
"code": "course_not_found",
"message": "Course not found.",
"request_id": "req_..."
}
}
```
Common codes for the Learn surface:
| Code | When |
|---|---|
| `404 course_not_found` | `id` doesn't match any row. |
| `404 enrollment_not_found` | Same for enrollments. |
| `404 certificate_not_found` | Same for certs (including `/verify/{vid}` misses). |
+ | `400 invalid_argument` | CourseService rejected the payload — message explains. |
| `400 invalid_argument` | The course service rejected the payload — message explains. |
| `400 missing_field` | A required body field was empty / absent. |
| `403 insufficient_scope` | Key doesn't have `learn:read` (or `learn:write`). |
## What's not in 1.3.0 (defer to a future minor)
- Import SCORM packages via API (use the admin UI today).
- Assessment item authoring + question-bank CRUD via API.
- Question pool CRUD via API.
- Per-course team membership for API keys (today they get tenant-wide
admin-equivalent capabilities).

mobieusCore API — mobieusLearn

The Learn surface of the public REST API. Manage courses, modules, lessons, activities, enrollments, attempts, certificates, certificate templates, and SCORM packages.

Added in API version 1.3.0 (2026-06-02).

Scopes

Scope Grants
learn:read List + read every Learn resource (courses, enrollments, attempts, certificates, templates, SCORM packages, nested entities).
learn:write Create / update / publish / archive courses, enroll users, cancel enrollments, revoke + regenerate certificates.

Generate a key at /admin/api-keys, tick the scopes you need. Pro / Creator Plus / Sovereign plans only — same plan gate as the rest of the public API.

Quick start

curl https://YOUR-TENANT.mobieus.io/api/v1/learn/courses \
  -H 'Authorization: Bearer mc_live_...'

Returns up to 50 courses ordered by id desc. Use ?cursor=...&limit=N (max 100) to page; next_cursor is null on the last page.

Resources

Courses

Method Path Scope What
GET /api/v1/learn/courses learn:read List courses (cursor-paginated).
GET /api/v1/learn/courses/{id} learn:read Get a single course (with credit info, version status, owner author).
POST /api/v1/learn/courses learn:write Create a course. Required: slug, title. Optional: subtitle, summary, language, completion_rule, estimated_minutes, credit_hours, credit_kind, credit_expires_after_months.
POST /api/v1/learn/courses/{id} learn:write Update. Any field from create is acceptable; omitted fields keep their value.
POST /api/v1/learn/courses/{id}/publish learn:write Move to published.
POST /api/v1/learn/courses/{id}/unpublish learn:write Revert to draft.
POST /api/v1/learn/courses/{id}/archive learn:write Archive (hides from learners; preserves enrollments).

Example: enroll-on-purchase flow

# 1. Create a course
curl -X POST https://YOUR-TENANT.mobieus.io/api/v1/learn/courses \
  -H 'Authorization: Bearer mc_live_...' -H 'Content-Type: application/json' \
  -d '{"slug":"intro-to-mobieus","title":"Intro to Mobieus","completion_rule":"all_required","credit_hours":2.5,"credit_kind":"CEU"}'

# 2. After a Stripe checkout completes, enroll the buyer
curl -X POST https://YOUR-TENANT.mobieus.io/api/v1/learn/enrollments \
  -H 'Authorization: Bearer mc_live_...' -H 'Content-Type: application/json' \
  -d '{"user_id":123,"course_id":42,"source":"stripe_purchase"}'

Nested entities (read-only)

| GET | /api/v1/learn/courses/{id}/modules | learn:read | Modules in a course. | | GET | /api/v1/learn/modules/{mid}/lessons | learn:read | Lessons in a module. | | GET | /api/v1/learn/lessons/{lid}/activities | learn:read | Activities in a lesson. |

Writes for modules / lessons / activities go through the admin UI; they enforce per-course team membership (see M9-2 in the changelog). Per-course capability scoping for the API key model lands in a future minor version.

Enrollments

| GET | /api/v1/learn/enrollments?course_id=&user_id= | learn:read | List, optionally filtered. | | GET | /api/v1/learn/enrollments/{id} | learn:read | Get one. | | POST | /api/v1/learn/enrollments | learn:write | Enroll a user. Body: {"user_id", "course_id", "source"?, "expires_at"?}. | | POST | /api/v1/learn/enrollments/{id}/cancel | learn:write | Cancel (status flips to cancelled). |

Attempts

| GET | /api/v1/learn/attempts?enrollment_id=&activity_id= | learn:read | List. | | GET | /api/v1/learn/attempts/{id} | learn:read | Get one (includes score_raw, score_scaled, passed, status, time_started_at, time_ended_at, total_time_seconds). |

Certificates

| GET | /api/v1/learn/certificates?user_id=&course_id= | learn:read | List. | | GET | /api/v1/learn/certificates/{id} | learn:read | Get one. | | GET | /api/v1/learn/certificates/verify/{vid} | learn:read | Look up by the 40-char verification id. Returns is_revoked: true|false. | | POST | /api/v1/learn/certificates/{id}/revoke | learn:write | Revoke. Body: {"reason": "..."}. Required. | | POST | /api/v1/learn/certificates/{id}/regenerate-pdf | learn:write | Rebuild the PDF (after editing a template). Verification URL unchanged. |

Certificate templates

| GET | /api/v1/learn/cert-templates | learn:read | List templates (per-tenant brand identity for certs). | | GET | /api/v1/learn/cert-templates/{id} | learn:read | Get one. |

SCORM packages

| GET | /api/v1/learn/scorm-packages | learn:read | List packages (any status). | | GET | /api/v1/learn/scorm-packages/{id} | learn:read | Get one with its SCO list. |

Importing a SCORM package via API isn't supported in 1.3.0 — use the admin UI at /admin/learn/scorm/new. The endpoint is on the roadmap.

Response envelope

Every endpoint returns:

{
  "data": ...,
  "next_cursor": "...",
  "request_id": "req_..."
}

next_cursor is null when there's no more data.

Errors

{
  "error": "course_not_found",
  "message": "Course not found.",
  "request_id": "req_..."
}

Common codes for the Learn surface:

Code When
404 course_not_found id doesn't match any row.
404 enrollment_not_found Same for enrollments.
404 certificate_not_found Same for certs (including /verify/{vid} misses).
400 invalid_argument CourseService rejected the payload — message explains.
400 missing_field A required body field was empty / absent.
403 insufficient_scope Key doesn't have learn:read (or learn:write).

What's not in 1.3.0 (defer to a future minor)

  • Import SCORM packages via API (use the admin UI today).
  • Assessment item authoring + question-bank CRUD via API.
  • Question pool CRUD via API.
  • Per-course team membership for API keys (today they get tenant-wide admin-equivalent capabilities).
# mobieusCore API — mobieusLearn

The Learn surface of the public REST API. Manage courses, modules,
lessons, activities, enrollments, attempts, certificates,
certificate templates, and SCORM packages.

Added in API version **1.3.0** (2026-06-02).

## Scopes

| Scope | Grants |
|---|---|
| `learn:read` | List + read every Learn resource (courses, enrollments, attempts, certificates, templates, SCORM packages, nested entities). |
| `learn:write` | Create / update / publish / archive courses, enroll users, cancel enrollments, revoke + regenerate certificates. |

Generate a key at **`/admin/api-keys`**, tick the scopes you need.
Pro / Creator Plus / Sovereign plans only — same plan gate as the
rest of the public API.

## Quick start

```bash
curl https://YOUR-TENANT.mobieus.io/api/v1/learn/courses \
  -H 'Authorization: Bearer mc_live_...'
```

Returns up to 50 courses ordered by id desc. Use `?cursor=...&limit=N`
(max 100) to page; `next_cursor` is null on the last page.

## Resources

### Courses

| Method | Path | Scope | What |
|---|---|---|---|
| `GET`  | `/api/v1/learn/courses`                  | `learn:read`  | List courses (cursor-paginated). |
| `GET`  | `/api/v1/learn/courses/{id}`             | `learn:read`  | Get a single course (with credit info, version status, owner author). |
| `POST` | `/api/v1/learn/courses`                  | `learn:write` | Create a course. Required: `slug`, `title`. Optional: subtitle, summary, language, completion_rule, estimated_minutes, credit_hours, credit_kind, credit_expires_after_months. |
| `POST` | `/api/v1/learn/courses/{id}`             | `learn:write` | Update. Any field from create is acceptable; omitted fields keep their value. |
| `POST` | `/api/v1/learn/courses/{id}/publish`     | `learn:write` | Move to published. |
| `POST` | `/api/v1/learn/courses/{id}/unpublish`   | `learn:write` | Revert to draft. |
| `POST` | `/api/v1/learn/courses/{id}/archive`     | `learn:write` | Archive (hides from learners; preserves enrollments). |

**Example: enroll-on-purchase flow**

```bash
# 1. Create a course
curl -X POST https://YOUR-TENANT.mobieus.io/api/v1/learn/courses \
  -H 'Authorization: Bearer mc_live_...' -H 'Content-Type: application/json' \
  -d '{"slug":"intro-to-mobieus","title":"Intro to Mobieus","completion_rule":"all_required","credit_hours":2.5,"credit_kind":"CEU"}'

# 2. After a Stripe checkout completes, enroll the buyer
curl -X POST https://YOUR-TENANT.mobieus.io/api/v1/learn/enrollments \
  -H 'Authorization: Bearer mc_live_...' -H 'Content-Type: application/json' \
  -d '{"user_id":123,"course_id":42,"source":"stripe_purchase"}'
```

### Nested entities (read-only)

| `GET` | `/api/v1/learn/courses/{id}/modules`     | `learn:read` | Modules in a course. |
| `GET` | `/api/v1/learn/modules/{mid}/lessons`    | `learn:read` | Lessons in a module. |
| `GET` | `/api/v1/learn/lessons/{lid}/activities` | `learn:read` | Activities in a lesson. |

Writes for modules / lessons / activities go through the admin UI;
they enforce per-course team membership (see M9-2 in the changelog).
Per-course capability scoping for the API key model lands in a
future minor version.

### Enrollments

| `GET`  | `/api/v1/learn/enrollments?course_id=&user_id=` | `learn:read`  | List, optionally filtered. |
| `GET`  | `/api/v1/learn/enrollments/{id}`                | `learn:read`  | Get one. |
| `POST` | `/api/v1/learn/enrollments`                     | `learn:write` | Enroll a user. Body: `{"user_id", "course_id", "source"?, "expires_at"?}`. |
| `POST` | `/api/v1/learn/enrollments/{id}/cancel`         | `learn:write` | Cancel (status flips to `cancelled`). |

### Attempts

| `GET` | `/api/v1/learn/attempts?enrollment_id=&activity_id=` | `learn:read` | List. |
| `GET` | `/api/v1/learn/attempts/{id}` | `learn:read` | Get one (includes score_raw, score_scaled, passed, status, time_started_at, time_ended_at, total_time_seconds). |

### Certificates

| `GET`  | `/api/v1/learn/certificates?user_id=&course_id=` | `learn:read`  | List. |
| `GET`  | `/api/v1/learn/certificates/{id}`                | `learn:read`  | Get one. |
| `GET`  | `/api/v1/learn/certificates/verify/{vid}`        | `learn:read`  | Look up by the 40-char verification id. Returns `is_revoked: true|false`. |
| `POST` | `/api/v1/learn/certificates/{id}/revoke`         | `learn:write` | Revoke. Body: `{"reason": "..."}`. Required. |
| `POST` | `/api/v1/learn/certificates/{id}/regenerate-pdf` | `learn:write` | Rebuild the PDF (after editing a template). Verification URL unchanged. |

### Certificate templates

| `GET` | `/api/v1/learn/cert-templates`      | `learn:read` | List templates (per-tenant brand identity for certs). |
| `GET` | `/api/v1/learn/cert-templates/{id}` | `learn:read` | Get one. |

### SCORM packages

| `GET` | `/api/v1/learn/scorm-packages`      | `learn:read` | List packages (any status). |
| `GET` | `/api/v1/learn/scorm-packages/{id}` | `learn:read` | Get one with its SCO list. |

Importing a SCORM package via API isn't supported in 1.3.0 — use the
admin UI at `/admin/learn/scorm/new`. The endpoint is on the roadmap.

## Response envelope

Every endpoint returns:

```json
{
  "data": ...,
  "next_cursor": "...",
  "request_id": "req_..."
}
```

`next_cursor` is null when there's no more data.

## Errors

```json
{
  "error": "course_not_found",
  "message": "Course not found.",
  "request_id": "req_..."
}
```

Common codes for the Learn surface:

| Code | When |
|---|---|
| `404 course_not_found` | `id` doesn't match any row. |
| `404 enrollment_not_found` | Same for enrollments. |
| `404 certificate_not_found` | Same for certs (including `/verify/{vid}` misses). |
| `400 invalid_argument` | CourseService rejected the payload — message explains. |
| `400 missing_field` | A required body field was empty / absent. |
| `403 insufficient_scope` | Key doesn't have `learn:read` (or `learn:write`). |

## What's not in 1.3.0 (defer to a future minor)

- Import SCORM packages via API (use the admin UI today).
- Assessment item authoring + question-bank CRUD via API.
- Question pool CRUD via API.
- Per-course team membership for API keys (today they get tenant-wide
  admin-equivalent capabilities).