> ## Documentation Index
> Fetch the complete documentation index at: https://opentrain.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# LMS course version rollouts

> REST endpoints for publishing an immutable course release and applying it safely to active learner assignments.

```text theme={null}
Base path: /api/public/v1/lms/courses/{courseId}
```

Course publication and active-assignment rollout are separate operations. Publishing creates an immutable release; rollout changes the effective release of an explicit assignment audience while preserving compatible learner state.

Reads require `lms:read`. Publish and rollout Apply require `lms:write`, a claimed employer account, organization/course authority, and an `Idempotency-Key` where shown.

## Publish

`POST /publish` requires an `Idempotency-Key` header:

```json theme={null}
{
  "expectedDraftRevision": 7,
  "confirmPublish": true
}
```

The response includes the immutable `version` and additive agent guidance:

```json theme={null}
{
  "version": {
    "id": "course-version-id",
    "courseId": "course-id",
    "contentHash": "sha256-content-hash"
  },
  "existingAssignments": {
    "updated": false,
    "reason": "PUBLISHED_VERSIONS_ARE_IMMUTABLE"
  },
  "nextAction": {
    "requiredForExistingAssignments": true,
    "operation": "lms.courses.rollout_preview",
    "courseId": "course-id",
    "courseVersionId": "course-version-id",
    "expectedContentHash": "sha256-content-hash",
    "applyOperation": "lms.courses.rollout",
    "statusOperation": "lms.courses.rollout_status",
    "receiptOperation": "lms.courses.rollout_receipt"
  }
}
```

## Preview rollout impact

`POST /rollout-preview` is read-only and takes no idempotency key:

```json theme={null}
{
  "toCourseVersionId": "course-version-id",
  "expectedToContentHash": "sha256-content-hash",
  "jobId": "job-id",
  "assignmentIds": null
}
```

Provide `jobId`, a non-empty `assignmentIds` array, or both. The response reports would-be assignment outcomes and counts. It is live information, not a concurrency fence.

## Apply rollout

`POST /rollout` requires a durable `Idempotency-Key` and explicit confirmation:

```json theme={null}
{
  "toCourseVersionId": "course-version-id",
  "expectedToContentHash": "sha256-content-hash",
  "jobId": "job-id",
  "assignmentIds": null,
  "confirmApply": true
}
```

Apply reclassifies each selected assignment from authoritative current state. Its receipt reports assignment-level outcomes and these counts:

* `updatedNow`
* `queued`
* `alreadyCurrent`
* `skippedTerminal`
* `blocked`
* `failed`

Ordinary learner activity after Preview does not reject the batch. A protected in-progress assessment queues for automatic safe-boundary migration. Submitted attempts keep their original version and scoring rules.

## Read status and receipts

`GET /rollout?toCourseVersionId=<id>&jobId=<id>` returns the newest matching receipt plus `pendingQueued`. Both filters are optional.

`GET /rollout/{operationKey}` returns the durable receipt for one Apply key plus its live queued count. Poll this record while deferred learners finish protected work.

## Idempotency and failures

Replaying the same Apply key with the identical request returns the stored receipt. Reusing it with a different request returns `409 CONFLICT`. One assignment's contention or failure is isolated and does not roll back successful assignments; inspect its typed reason and retry that selected assignment under a fresh key after remediation.

Rollout never cancels or recreates assignments, resets unrelated progress, or silently regrades submitted attempts.

See [Update an assigned LMS course](/docs/developers/guides/update-assigned-lms-courses), [CLI commands](/docs/developers/cli/commands), and [MCP tools](/docs/developers/mcp/lms-retraining).
