> ## Documentation Index
> Fetch the complete documentation index at: https://theteam-13fc7f49.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Lessons

Manage the lessons inside a chapter: list, create, reorder, show, update, permanently delete, and toggle a student's completion status.

All endpoints are authenticated — they run behind `auth:sanctum`, the "user is not blocked" check, and the per-company policy middleware. Always send your Sanctum token as a `Bearer` header.

## Table of contents

* **Base URL & Headers**
* **List lessons**
* **Reorder lessons**
* **Create a lesson**
* **Retrieve a lesson**
* **Update a lesson**
* **Permanently delete a lesson**
* **Toggle lesson validation for a student**
* **Postman setup**

***

## Base URL & Headers

```text theme={null}
Base URL: {{base_url}}/api
```

```text theme={null}
Accept: application/json
Authorization: Bearer {{token}}
Content-Type: application/json
```

Dates are returned in `Y-m-d H:i:s` format. Boolean query params accept `true`, `false`, `1`, `0`.

> A lesson's `position` is assigned automatically on creation (next available slot per chapter). You only send `position` when you want to change it via **update** or **reorder**.

***

## List lessons

`GET /lessons`

Returns a paginated list of lessons belonging to a given chapter, ordered by `position`.

### Query parameters

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `chapter_id` | integer | Yes | Must exist in the `chapters` table. |

### Example request

```text theme={null}
GET {{base_url}}/api/lessons?chapter_id=40
Accept: application/json
Authorization: Bearer {{token}}
```

### Response — `200 OK`

```text theme={null}
{
  "current_page": 1,
  "data": [
    {
      "id": 101,
      "chapter_id": 40,
      "label": "Lesson 1 — Introduction",
      "type": "video",
      "position": 1,
      "content": "<p>Welcome to the first lesson.</p>",
      "created_at": "2026-04-01 09:25:00",
      "updated_at": "2026-04-01 09:25:00",
      "archived_at": null,
      "deleted_at": null
    },
    {
      "id": 102,
      "chapter_id": 40,
      "label": "Lesson 2 — Key Concepts",
      "type": "document",
      "position": 2,
      "content": "<p>...</p>"
    }
  ],
  "per_page": 15,
  "total": 3,
  "last_page": 1
}
```

***

## Reorder lessons

`POST /lessons/reorder`

Bulk-updates the `position` of multiple lessons in a single call. The request body is a **bare array** — not an object.

### Body

An array of `{ id, position }` pairs.

| Field | Type | Required | Description |
| :- | :- | :- | :- |
| `id` | integer | Yes | Must exist in `lessons`. |
| `position` | integer | Yes | Minimum `1`. |

### Example request

```text theme={null}
POST {{base_url}}/api/lessons/reorder
Accept: application/json
Authorization: Bearer {{token}}
Content-Type: application/json
```

```text theme={null}
[
  { "id": 101, "position": 2 },
  { "id": 102, "position": 1 },
  { "id": 103, "position": 3 }
]
```

### Response — `200 OK`

```text theme={null}
{ "message": "Lessons reordered successfully." }
```

***

## Create a lesson

`POST /lesson`

Creates a lesson inside a chapter. The `position` is assigned automatically (one higher than the current maximum for that chapter).

### Body parameters

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `chapter_id` | integer | Yes | Must exist in `chapters`. |
| `label` | string | Yes | 1–255 characters. |
| `type` | string | No | `document` (default) or `video`. Drives the icon the apps show next to the lesson; any other value is a `422`. |
| `content` | string | Yes | Must be present in the payload. May be an empty string, but not missing. HTML is allowed. |

### Example request

```text theme={null}
POST {{base_url}}/api/lesson
Accept: application/json
Authorization: Bearer {{token}}
Content-Type: application/json
```

```text theme={null}
{
  "chapter_id": 40,
  "label": "Lesson 1 — Introduction",
  "type": "video",
  "content": "<p>Welcome to the first lesson.</p>"
}
```

### Responses

`200 OK`

```text theme={null}
{ "message": "Lesson created successfully." }
```

`422 Unprocessable Entity` — unknown type

```json theme={null}
{
  "message": "The selected type is invalid.",
  "errors": { "type": ["The selected type is invalid."] }
}
```

***

## Retrieve a lesson

`GET /lesson/{id}`

Returns a single lesson.

### Example request

```text theme={null}
GET {{base_url}}/api/lesson/101
Accept: application/json
Authorization: Bearer {{token}}
```

### Response — `200 OK`

```text theme={null}
{
  "id": 101,
  "chapter_id": 40,
  "label": "Lesson 1 — Introduction",
  "position": 1,
  "content": "<p>Welcome to the first lesson.</p>",
  "created_at": "2026-04-01 09:25:00",
  "updated_at": "2026-04-01 09:25:00",
  "archived_at": null,
  "deleted_at": null
}
```

### `404 Not Found`

```text theme={null}
{ "message": "No query results for model [App\\Models\\Lesson] 101" }
```

***

## Update a lesson

`POST /lesson/{id}`

Updates an existing lesson. Unlike create, `position` is **required** here — send the current position if you don't want to move the lesson.

### Body parameters

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `label` | string | Yes | 1–255 characters. |
| `position` | integer | Yes | Minimum `1`. |
| `type` | string | No | `document` or `video`. When omitted the stored type is kept (older clients never send it). |
| `content` | string | Yes | Must be present in the payload (may be empty). |

### Example request

```text theme={null}
POST {{base_url}}/api/lesson/101
Accept: application/json
Authorization: Bearer {{token}}
Content-Type: application/json
```

```text theme={null}
{
  "label": "Lesson 1 — Introduction (revised)",
  "position": 1,
  "type": "document",
  "content": "<p>Updated content.</p>"
}
```

### Response — `200 OK`

```text theme={null}
{ "message": "Lesson updated successfully." }
```

***

## Permanently delete a lesson

`DELETE /lesson/{id}/hard_delete`

Force-deletes the lesson. The operation is irreversible.

> ⚠️ This hard-deletes the record — it does not soft-delete. If you need to keep history, archive upstream (e.g. the chapter or training) instead.

### Example request

```text theme={null}
DELETE {{base_url}}/api/lesson/101/hard_delete
Accept: application/json
Authorization: Bearer {{token}}
```

### Response — `200 OK`

```text theme={null}
{ "message": "Lesson deleted successfully." }
```

***

## Toggle lesson validation for a student

`POST /lesson/{id}/toggle_lesson_validation`

Marks (or un-marks) a lesson as completed for a specific student. This attaches or detaches the student from the lesson in the `student_lesson` pivot table. The response includes the fresh user progress computed by the Dashboard service, so the client can refresh its progress UI in the same round-trip.

### Body parameters

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `user_id` | integer | Yes | Must exist in `users`. The student whose completion status is toggled. |
| `toggle_lesson_validation` | boolean | Yes | `true` to mark the lesson as completed, `false` to un-mark it. |

### Example request — mark as completed

```text theme={null}
POST {{base_url}}/api/lesson/101/toggle_lesson_validation
Accept: application/json
Authorization: Bearer {{token}}
Content-Type: application/json
```

```text theme={null}
{
  "user_id": 88,
  "toggle_lesson_validation": true
}
```

### Example request — un-mark (remove completion)

```text theme={null}
{
  "user_id": 88,
  "toggle_lesson_validation": false
}
```

### Response — `200 OK`

```text theme={null}
{
  "message": "The relationship between user and lesson is successfully updated.",
  "user_progress": {
    "user_id": 88,
    "trainings": [
      {
        "training_id": 12,
        "label": "Intro to PMP",
        "total_lessons": 18,
        "completed_lessons": 5,
        "progress_percentage": 27.78
      }
    ]
  }
}
```

The exact shape of `user_progress` matches whatever `DashboardsController@userProgress` returns for that user. Treat the example above as illustrative, not authoritative.

### Error responses

`422 Unprocessable Entity` — missing or invalid fields:

```text theme={null}
{
  "message": "The given data was invalid.",
  "errors": {
    "user_id": ["The user id field is required."],
    "toggle_lesson_validation": ["The toggle lesson validation field must be true or false."]
  }
}
```

***

## Postman setup

1. **Environment variables.**
   * `base_url` — your API root, e.g. `http://localhost:8000`
   * `token` — the Sanctum token returned by `POST /auth/login`
2. **Collection-level headers.**
   * `Accept: application/json`
   * `Authorization: Bearer {{token}}`
3. **Request bodies.**
   * JSON bodies → **Body → raw → JSON**
   * For **Reorder**, the body is a bare JSON array, not an object wrapped in a key.
   * `content` must always be present on create and update — use an empty string (`""`) rather than omitting it if the lesson has no body text.
   * `type` is optional (`document` by default). It is set by hand in the training editor: the backend never derives it from the HTML, and every lesson created before 16/10/2026 is `document` until an author switches it to `video`.
4. **Smoke-test flow.**
   1. `POST /lesson` — create
   2. `GET /lessons?chapter_id={id}` — verify it appears
   3. `POST /lesson/{id}` — update
   4. `POST /lessons/reorder` — reorder with peers
   5. `POST /lesson/{id}/toggle_lesson_validation` with `true` — mark as completed
   6. `POST /lesson/{id}/toggle_lesson_validation` with `false` — un-mark
   7. `DELETE /lesson/{id}/hard_delete` — cleanup


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.