> ## 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.

# Individualized retraining API

> REST endpoints for draft retraining cases, no-write assignment plans, exact-one assignment, live reporting, and reusable skill-gap modules.

The retraining API orchestrates existing LMS and Project To-do operations for one worker. It does not create a second course, assignment, grading, or retry system.

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

## Discover capabilities first

Read rollout and authorization before the scoped LMS schema:

```bash theme={null}
curl -sS https://app.opentrain.ai/api/public/v1/capabilities \
  -H "Authorization: Bearer $OT_API_TOKEN"
```

Inspect the LMS retraining family under `agentSurfaces`: the family status is `disabled`, `rollout_gated`, `unauthorized`, or `available`, and each operation has its own `authorized` value. Treat every status except `available` as fail-closed.

Then read the canonical LMS contract before composing requests:

```bash theme={null}
curl -sS https://app.opentrain.ai/api/public/v1/lms/capabilities \
  -H "Authorization: Bearer $OT_API_TOKEN"
```

Inspect `capabilities.retraining` for operations, modes, exact-one recipient and due-date rules, plan receipt bindings, receipt lifetime, close outcomes, and human checkpoints. The same response describes current course, lesson, assessment, asset, Preview, and mastery capabilities.

## Endpoints

| Method  | Path                     | Purpose                                                                       |
| ------- | ------------------------ | ----------------------------------------------------------------------------- |
| `POST`  | `/cases`                 | Create one employer-only `DRAFT` case.                                        |
| `GET`   | `/cases`                 | List cases with bounded cursor pagination.                                    |
| `GET`   | `/cases/{caseId}`        | Read a case plus live assignment, review, and bounded run projections.        |
| `PATCH` | `/cases/{caseId}`        | Update a draft under `expectedStateRevision`.                                 |
| `POST`  | `/cases/{caseId}/plan`   | Produce a signed, strictly no-write assignment plan.                          |
| `POST`  | `/cases/{caseId}/assign` | Confirm the reviewed plan and create one Project To-do assignment atomically. |
| `POST`  | `/cases/{caseId}/close`  | Close the case with an outcome that agrees with canonical assignment state.   |
| `GET`   | `/modules`               | List reusable published skill-gap modules with bounded cursor pagination.     |
| `POST`  | `/modules`               | Register or relabel a published course module.                                |

## Authorization

Reads require `lms:read`. Writes and the protected no-write plan require `lms:write`. Every operation enforces organization and job access. Worker names are masked. Answer keys, correct values, and other workers' submissions are never included.

Create, update, assign, close, and module-registration requests require an `Idempotency-Key` header. Replaying the same key with the same request returns the stored result. Reusing it with a different request fails with `409 CONFLICT`. Planning intentionally takes no idempotency key because it performs no writes.

## Create a case

`POST /cases` requires an `Idempotency-Key` header and an exact-one worker reference:

```json theme={null}
{
  "jobId": "job-id",
  "worker": { "contractId": "contract-id" },
  "observedMistake": "The submitted reconciliation omitted the cutoff check.",
  "expectedBehavior": "Reconcile the full population and document the cutoff test before submission.",
  "workerSummary": "Review the reconciliation and cutoff workflow",
  "mode": "MASTERY",
  "courseId": "course-id",
  "moduleKey": "reconciliation-cutoff",
  "masteryRunLimit": 3,
  "dueOffsetMinutes": 1440,
  "priority": "URGENT",
  "required": true,
  "blockingMode": "IMMEDIATE",
  "instructionRefs": ["instruction-page-id"]
}
```

Use either `worker.contractId` or `worker.workerName`, never both. Exact-name resolution fails closed on zero or multiple matches.

Use exactly one of `dueOffsetMinutes` or `absoluteDue`:

```json theme={null}
{
  "absoluteDue": {
    "localDate": "2026-09-01",
    "localTime": "17:00",
    "timeZone": "America/New_York"
  }
}
```

## Update a draft

`PATCH /cases/{caseId}` requires `expectedStateRevision` and an `Idempotency-Key` header:

```json theme={null}
{
  "expectedStateRevision": 2,
  "patch": {
    "courseId": "course-id",
    "moduleKey": "reconciliation-cutoff"
  }
}
```

The operation updates only a `DRAFT` case. A stale revision returns `409 CONFLICT`; read the current case before deciding whether to retry.

## Plan and assign

`POST /cases/{caseId}/plan` takes no idempotency key and performs no writes. The response includes:

* the masked recipient and stable contract ID;
* the exact published course version, module, and content hash;
* the case revision and capture hash;
* the exact frozen due instant;
* mastery eligibility and warnings;
* zero plan-side writes, notifications, and attempts;
* the exact confirmation effects;
* a request hash and short-lived signed receipt.

An authorized employer must review and explicitly approve the exact masked recipient, version and content hash, frozen due instant, effects, request hash, and receipt before assignment.

After that approval, confirm with `POST /cases/{caseId}/assign`, an `Idempotency-Key` header, and:

```json theme={null}
{
  "expectedStateRevision": 2,
  "planReceiptToken": "signed-plan-token",
  "confirmAssign": true
}
```

`confirmAssign: true` records the approved mutation; it does not authorize an agent to approve its own plan. The receipt becomes stale when the captured case, course content, course version, or revision changes. Re-plan and obtain approval for the new result instead of forcing a stale assignment.

## Read and close

`GET /cases/{caseId}` returns employer-safe case details plus live projections from the canonical assignment and review readers. Run history is bounded; follow the returned cursor through the canonical LMS run-history operation when more rows exist.

`GET /cases` and `GET /modules` return `hasMore` and an opaque `nextCursor`. Pass `nextCursor` back unchanged as `cursor` and continue until `hasMore === false` before reporting a complete inventory. A cursor outside the authorized, filtered result set returns `400 BAD_REQUEST`.

Closing is terminal for that occurrence. `PASSED` requires a canonically completed assignment. `FAILED` requires an exhausted failure parked with the employer. `CANCELLED` and `SUPERSEDED` do not cancel an active assignment; resolve that assignment through its canonical operation first.

`POST /cases/{caseId}/close` requires an `Idempotency-Key` header and:

```json theme={null}
{
  "expectedStateRevision": 4,
  "outcome": "PASSED",
  "reason": "Completed the assigned remediation",
  "confirmClose": true
}
```

## Errors

Failures use the shared envelope:

```json theme={null}
{
  "error": "Human-readable message",
  "code": "CONFLICT",
  "requestId": "request-id",
  "details": { "reason": "machine-readable-context" }
}
```

Branch on `code` and structured `details`, not `error`. Common retraining statuses are: create `404/409/422`, update `409/422`, plan `404/409/422`, assign `409`, close `409`, and list `400`. See [Errors, Pagination, and Rate Limits](/docs/developers/concepts/errors-pagination-limits) for the shared meanings and retry rules.

## Register a reusable module

`POST /modules` takes an `Idempotency-Key` header:

```json theme={null}
{
  "courseId": "course-id",
  "moduleKey": "reconciliation-cutoff",
  "label": "Reconciliation and cutoff",
  "description": "Targeted remediation for reconciliation completeness and period cutoff."
}
```

The module must exist in the course's current published version. Registration stores reusable metadata only; it does not edit the course.

Use the machine-readable [OpenAPI document](https://app.opentrain.ai/api/public/v1/openapi.json) for the complete request and response schemas.
