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

# Classrooms API Endpoints

> List, open, create, update, archive and delete classrooms, and what each role can see of a classroom's members.

##### *General - Classrooms API Documentation and Postman Guide.*

This page covers the endpoints of `ClassroomController`. A classroom belongs to one company, has one instructor and any number of learners (the `classroom_user` pivot), and carries an HTML `content`.

All endpoints run behind `auth:sanctum`, the "user is not blocked" check, and the per-company policy middleware. Every query is scoped to the caller's company (`auth()->user()->company_id`).

The JSON of the three read endpoints is built by `app/Services/ClassroomPayloadService.php`.

> [!NOTE]
>
> A classroom's group chat is a separate **chatroom** with the same slug (its own tables and endpoints, created after the classroom): see [Chatrooms API Endpoints](https://theteam-13fc7f49.mintlify.site/api-reference/chatrooms.md). Nothing on this page creates, edits or deletes it.

***

## Table of Contents

1. **Base URL & Headers**
2. **Who Sees What**
   * **2.1 Staff and Students**
   * **2.2 The Person Object**
   * **2.3 Member Counts**
3. **Endpoints**
   * **3.1 List Classrooms**
   * **3.2 Show a Classroom**
   * **3.3 List the Classrooms of a Student**
   * **3.4 Create a Classroom**
   * **3.5 Update a Classroom**
   * **3.6 Archive a Classroom**
   * **3.7 Unarchive a Classroom**
   * **3.8 Permanently Delete a Classroom**
4. **Known Implementation Notes**
5. **Postman Setup**

***

## 1. Base URL & Headers

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

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

***

## 2. Who Sees What

### 2.1 Staff and Students

The read endpoints separate two kinds of callers:

| Caller | Roles | Can reach | Sees of a learner or instructor |
| :- | :- | :- | :- |
| **Staff** | Instructor (2), Content Manager (3), Admin (4), Super-Admin (5) | Every classroom of their company | The person object **with** `email` |
| **Student** | Student (1), and any other role | Only the classrooms they are a learner of | The person object **without** `email` |

> [!NOTE]
>
> A student never receives another user's email, phone number, birthday, 2FA state, owned trainings or exams, classroom slugs, coupons, loyalty or gamification points, revenue values, or public profile fields from these endpoints. Only the fields of the person object below are returned.

### 2.2 The Person Object

Learners (`users`) and the `instructor` share one shape:

| Field | Type | Description |
| :- | :- | :- |
| `id` | integer | User id. |
| `first_name` | string | First name. |
| `last_name` | string | Last name. |
| `fullname` | string | First and last name, trimmed. Empty when the user has no name (it never falls back to the email). |
| `image_id` | integer or null | Id of the avatar file. |
| `image` | object or null | The avatar: `id`, `type` (1 local, 2 external, 3 R2) and `url`. |
| `created_at` | string | Account creation date (`Y-m-d H:i:s`), shown as "member since". |
| `email` | string | **Staff callers only.** The key is absent for students. |

### 2.3 Member Counts

Every classroom returned by a read endpoint carries two counts, so a list never has to return its members to count them:

| Field | Description |
| :- | :- |
| `users_count` | Number of learners. Archived and deleted users are not counted. |
| `members_count` | Learners plus the instructor, each person counted once (an instructor who is also a learner of the classroom is not counted twice). |

***

## 3. Endpoints

## 3.1 List Classrooms

`GET /classrooms`

Paginated list used by the admin, instructor and content manager tables. Staff get every classroom of their company. A student who calls it only gets the classrooms they are a learner of, without `user_ids`.

**Query parameters**

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `show_archived` | integer | No | `1` lists only archived classrooms. Default `0` (active classrooms). |
| `instructor_id` | integer | No | Only the classrooms of this instructor. |
| `student_id` | integer | No | Only the classrooms this user is a learner of. |
| `search` | string | No | Matches the classroom title, description, or the instructor's first name, last name or email. |
| `sort_by` | string | No | Column to sort by. Default `created_at`. |
| `sort_order` | string | No | `asc` or `desc`. Default `desc`. |
| `page` | integer | No | Default `1`. |
| `per_page` | integer | No | Default `10`. |

**Example request**

```text theme={null}
GET {{base_url}}/api/classrooms?page=1&per_page=10
Accept: application/json
Authorization: Bearer {{token}}
```

**Responses**

> [tab] 200
>
> ✅ 200 OK (staff caller)
>
> ```json theme={null}
> {
>   "current_page": 1,
>   "data": [
>     {
>       "id": 2,
>       "classroom_title": "PMP coaching group",
>       "classroom_slug": "pmp-coaching-group",
>       "classroom_description": "Weekly coaching sessions",
>       "conversation_url": null,
>       "classroom_color": "ff0026",
>       "expire_at": "2026-10-31 00:00:00",
>       "content": "<p>Welcome to the classroom.</p>",
>       "instructor_id": 2,
>       "company_id": 1,
>       "archived_at": null,
>       "deleted_at": null,
>       "created_at": "2026-06-21 20:09:25",
>       "updated_at": "2026-06-22 13:42:13",
>       "user_ids": [14, 15, 16],
>       "users_count": 3,
>       "members_count": 4,
>       "instructor": {
>         "id": 2,
>         "first_name": "Jane",
>         "last_name": "Doe",
>         "fullname": "Jane Doe",
>         "image_id": 40,
>         "image": {
>           "id": 40,
>           "type": 3,
>           "url": "https://cdn.example.com/avatars/jane.png"
>         },
>         "created_at": "2026-06-18 10:49:23",
>         "email": "jane.doe@example.com"
>       }
>     }
>   ],
>   "first_page_url": "{{base_url}}/api/classrooms?page=1",
>   "from": 1,
>   "last_page": 1,
>   "last_page_url": "{{base_url}}/api/classrooms?page=1",
>   "links": [],
>   "next_page_url": null,
>   "path": "{{base_url}}/api/classrooms",
>   "per_page": 10,
>   "prev_page_url": null,
>   "to": 1,
>   "total": 1
> }
> ```

> [tab] 400
>
> ⚠️ 400 Bad Request
>
> ```json theme={null}
> { "message": "No company_id found for user" }
> ```

> [tab] 401
>
> ⚠️ 401 Unauthorized
>
> ```json theme={null}
> { "message": "Unauthenticated." }
> ```

> [tab] 500
>
> ❌ 500 Internal Server Error
>
> ```json theme={null}
> { "message": "<exception message>" }
> ```

> `user_ids` is returned to staff only (it feeds the learner picker and "Notify classroom"). For a student caller the key is absent and the `instructor` has no `email`.

***

## 3.2 Show a Classroom

`GET /classroom/{id}`

Returns one classroom with its `content`, its instructor and **every** learner. Staff can open any classroom of their company. A student can only open a classroom they are a learner of.

**Path parameters**

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `id` | integer | Yes | Classroom id. Must belong to the caller's company. |

**Query parameters**

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `show_archived` | integer | No | `1` to open an archived classroom. Default `0`: an archived classroom answers `404`. |

**Example request**

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

**Responses**

> [tab] 200
>
> ✅ 200 OK (student caller: no `email` on anyone)
>
> ```json theme={null}
> {
>   "id": 2,
>   "classroom_title": "PMP coaching group",
>   "classroom_slug": "pmp-coaching-group",
>   "classroom_description": "Weekly coaching sessions",
>   "conversation_url": null,
>   "classroom_color": "ff0026",
>   "expire_at": "2026-10-31 00:00:00",
>   "content": "<p>Welcome to the classroom.</p>",
>   "instructor_id": 2,
>   "company_id": 1,
>   "archived_at": null,
>   "deleted_at": null,
>   "created_at": "2026-06-21 20:09:25",
>   "updated_at": "2026-06-22 13:42:13",
>   "users_count": 2,
>   "members_count": 3,
>   "users": [
>     {
>       "id": 14,
>       "first_name": "Sami",
>       "last_name": "Zouari",
>       "fullname": "Sami Zouari",
>       "image_id": null,
>       "image": null,
>       "created_at": "2026-06-18 11:47:03"
>     },
>     {
>       "id": 15,
>       "first_name": "Amira",
>       "last_name": "Ben Ali",
>       "fullname": "Amira Ben Ali",
>       "image_id": 52,
>       "image": {
>         "id": 52,
>         "type": 3,
>         "url": "https://cdn.example.com/avatars/amira.png"
>       },
>       "created_at": "2026-06-18 11:50:09"
>     }
>   ],
>   "instructor": {
>     "id": 2,
>     "first_name": "Jane",
>     "last_name": "Doe",
>     "fullname": "Jane Doe",
>     "image_id": null,
>     "image": null,
>     "created_at": "2026-06-18 10:49:23"
>   }
> }
> ```
>
> For a staff caller, each entry of `users` and the `instructor` also carry `"email"`.

> [tab] 401
>
> ⚠️ 401 Unauthorized
>
> ```json theme={null}
> { "message": "Unauthenticated." }
> ```

> [tab] 403
>
> ⚠️ 403 Forbidden (a student who is not a learner of this classroom)
>
> ```json theme={null}
> { "message": "You are not a member of this classroom." }
> ```

> [tab] 404
>
> ⚠️ 404 Not Found (unknown id, another company's classroom, or archived without `show_archived=1`)
>
> ```json theme={null}
> { "message": "No query results for model [App\\Models\\Classroom] 2" }
> ```

***

## 3.3 List the Classrooms of a Student

`GET /classrooms/by-student`

Paginated list of the active classrooms a student is a learner of. It feeds the student's "My classrooms" cards and the admin's learner analytics.

A student can only pass their own id. Staff can pass the id of any student of their company.

To keep the list light, an item has **no `content`** and only a **preview of up to 3 learners** (for the avatars). Use `users_count` / `members_count` for the totals, and `GET /classroom/{id}` for the content and the full member list.

**Query parameters**

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `student_id` | integer | Yes | The student. For a student caller it must be their own id. |
| `search` | string | No | Matches the classroom title, description, or the instructor's first name, last name or email. |
| `page` | integer | No | Default `1`. |
| `per_page` | integer | No | Default `15`. |

**Example request**

```text theme={null}
GET {{base_url}}/api/classrooms/by-student?student_id=14&page=1&per_page=8
Accept: application/json
Authorization: Bearer {{token}}
```

**Responses**

> [tab] 200
>
> ✅ 200 OK (student caller)
>
> ```json theme={null}
> {
>   "current_page": 1,
>   "data": [
>     {
>       "id": 2,
>       "classroom_title": "PMP coaching group",
>       "classroom_slug": "pmp-coaching-group",
>       "classroom_description": "Weekly coaching sessions",
>       "conversation_url": null,
>       "classroom_color": "ff0026",
>       "expire_at": "2026-10-31 00:00:00",
>       "instructor_id": 2,
>       "company_id": 1,
>       "archived_at": null,
>       "deleted_at": null,
>       "created_at": "2026-06-21 20:09:25",
>       "updated_at": "2026-06-22 13:42:13",
>       "users_count": 9,
>       "members_count": 10,
>       "users": [
>         {
>           "id": 14,
>           "first_name": "Sami",
>           "last_name": "Zouari",
>           "fullname": "Sami Zouari",
>           "image_id": null,
>           "image": null,
>           "created_at": "2026-06-18 11:47:03"
>         },
>         {
>           "id": 15,
>           "first_name": "Amira",
>           "last_name": "Ben Ali",
>           "fullname": "Amira Ben Ali",
>           "image_id": null,
>           "image": null,
>           "created_at": "2026-06-18 11:50:09"
>         },
>         {
>           "id": 16,
>           "first_name": "Hamza",
>           "last_name": "Trabelsi",
>           "fullname": "Hamza Trabelsi",
>           "image_id": null,
>           "image": null,
>           "created_at": "2026-06-18 11:48:17"
>         }
>       ],
>       "instructor": {
>         "id": 2,
>         "first_name": "Jane",
>         "last_name": "Doe",
>         "fullname": "Jane Doe",
>         "image_id": null,
>         "image": null,
>         "created_at": "2026-06-18 10:49:23"
>       }
>     }
>   ],
>   "first_page_url": "{{base_url}}/api/classrooms/by-student?page=1",
>   "from": 1,
>   "last_page": 1,
>   "last_page_url": "{{base_url}}/api/classrooms/by-student?page=1",
>   "links": [],
>   "next_page_url": null,
>   "path": "{{base_url}}/api/classrooms/by-student",
>   "per_page": 8,
>   "prev_page_url": null,
>   "to": 1,
>   "total": 1
> }
> ```
>
> For a staff caller, each entry of `users` and the `instructor` also carry `"email"`.

> [tab] 400
>
> ⚠️ 400 Bad Request
>
> ```json theme={null}
> { "message": "student_id is required" }
> ```

> [tab] 401
>
> ⚠️ 401 Unauthorized
>
> ```json theme={null}
> { "message": "Unauthenticated." }
> ```

> [tab] 403
>
> ⚠️ 403 Forbidden (a student asking for another student's classrooms)
>
> ```json theme={null}
> { "message": "You can only list your own classrooms." }
> ```

> [tab] 500
>
> ❌ 500 Internal Server Error
>
> ```json theme={null}
> { "message": "<exception message>" }
> ```

> A `student_id` from another company returns an empty list: the query only reads the caller's company.

***

## 3.4 Create a Classroom

`POST /classroom/create`

Creates a classroom in the caller's company. Used by the admin, instructor and content manager screens.

**Body parameters**

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `classroom_title` | string | Yes | Up to 255 characters. |
| `classroom_slug` | string | No | **Generated by the backend when omitted or empty** (since 20/10/2026, like training and quiz slugs): the title is slugified (`"PMP Coaching Group"` → `pmp-coaching-group`) and suffixed `-2`, `-3`… until unique (archived and soft-deleted classrooms count). When sent, it is lowercased, must match `^[a-z0-9()\-,._=\[\]:]+$` (up to 255 characters) and be unique. The apps never send it on create. |
| `classroom_description` | string | No | Free text. |
| `conversation_url` | string | No | Must be a valid URL. |
| `classroom_color` | string | No | Hex color without `#`, up to 7 characters. Default `2196f3`. |
| `expire_at` | date | No | Default: one month after creation. |
| `content` | string | No | HTML content of the classroom. |
| `instructor_id` | integer | Yes | Must exist in `users`. |
| `user_ids` | array of integers | No | The learners. Each id must exist in `users`. |

**Example request**

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

```json theme={null}
{
  "classroom_title": "PMP coaching group",
  "classroom_description": "Weekly coaching sessions",
  "classroom_color": "ff0026",
  "expire_at": "2026-10-31 00:00:00",
  "content": "<p>Welcome to the classroom.</p>",
  "instructor_id": 2,
  "user_ids": [14, 15, 16]
}
```

**Responses**

> [tab] 201
>
> ✅ 201 Created
>
> ```json theme={null}
> {
>   "id": 2,
>   "classroom_title": "PMP coaching group",
>   "classroom_slug": "pmp-coaching-group",
>   "classroom_description": "Weekly coaching sessions",
>   "classroom_color": "ff0026",
>   "expire_at": "2026-10-31 00:00:00",
>   "content": "<p>Welcome to the classroom.</p>",
>   "instructor_id": 2,
>   "company_id": 1,
>   "created_at": "2026-06-21 20:09:25",
>   "updated_at": "2026-06-21 20:09:25",
>   "instructor": { "id": 2, "first_name": "Jane", "last_name": "Doe" }
> }
> ```
>
> The `instructor` object is the instructor's user record as loaded by the controller (abbreviated here).

> [tab] 401
>
> ⚠️ 401 Unauthorized
>
> ```json theme={null}
> { "message": "Unauthenticated." }
> ```

> [tab] 422
>
> ⚠️ 422 Validation Error (since 20/10/2026; it was a `500` before)
>
> ```json theme={null}
> {
>   "message": "The classroom slug has already been taken.",
>   "errors": { "classroom_slug": ["The classroom slug has already been taken."] }
> }
> ```
>
> Other cases: a missing `classroom_title` or `instructor_id`, a `classroom_slug` with forbidden characters (uppercase letters, spaces).

> [tab] 500
>
> ❌ 500 Internal Server Error
>
> ```json theme={null}
> { "message": "..." }
> ```

**Notes**

* The learners receive an in-app "You have been added to a classroom" notification (no email).
* The classroom slug (generated or supplied) is returned as `classroom_slug` and added to each learner's `classroom_joined_slugs` and to the instructor's `classroom_admin_slugs`. The apps pass it to `POST /chatrooms` on the stepper's next step.

***

## 3.5 Update a Classroom

`PUT /classroom/update/{id}`

Updates the fields that are present and not null. Sending `user_ids` **replaces** the learner list.

**Path parameters**

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `id` | integer | Yes | Classroom id. |

**Body parameters**

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `classroom_title` | string | No | New title. |
| `classroom_slug` | string | No | **This is where the slug is edited** (the apps unlock the field on the edit page). Lowercased, same format as on create, unique among the other classrooms (archived and soft-deleted ones count). Omitted or empty = kept; a classroom that never had one (older than the slug column) gets one from its title. |
| `classroom_description` | string | No | New description. |
| `conversation_url` | string | No | New URL. |
| `classroom_color` | string | No | New color. |
| `expire_at` | date | No | New expiry date. |
| `content` | string | No | New HTML content. |
| `instructor_id` | integer | No | New instructor. |
| `user_ids` | array of integers | No | The complete new learner list (sync). |

**Example request**

```text theme={null}
PUT {{base_url}}/api/classroom/update/2
Accept: application/json
Authorization: Bearer {{token}}
Content-Type: application/json
```

```json theme={null}
{
  "classroom_title": "PMP coaching group (autumn)",
  "user_ids": [14, 15, 16, 27]
}
```

**Responses**

> [tab] 200
>
> ✅ 200 OK
>
> ```json theme={null}
> {
>   "id": 2,
>   "classroom_title": "PMP coaching group (autumn)",
>   "classroom_slug": "pmp-coaching-group",
>   "classroom_description": "Weekly coaching sessions",
>   "conversation_url": null,
>   "classroom_color": "ff0026",
>   "expire_at": "2026-10-31 00:00:00",
>   "content": "<p>Welcome to the classroom.</p>",
>   "instructor_id": 2,
>   "company_id": 1,
>   "archived_at": null,
>   "deleted_at": null,
>   "created_at": "2026-06-21 20:09:25",
>   "updated_at": "2026-10-04 15:02:11",
>   "instructor": { "id": 2, "first_name": "Jane", "last_name": "Doe" },
>   "user_ids": [14, 15, 16, 27]
> }
> ```
>
> The `instructor` object is the instructor's user record as loaded by the controller (abbreviated here).

> [tab] 401
>
> ⚠️ 401 Unauthorized
>
> ```json theme={null}
> { "message": "Unauthenticated." }
> ```

> [tab] 422
>
> ⚠️ 422 Validation Error (since 20/10/2026; it was a `500` before)
>
> ```json theme={null}
> {
>   "message": "The classroom slug has already been taken.",
>   "errors": { "classroom_slug": ["The classroom slug has already been taken."] }
> }
> ```

> [tab] 500
>
> ❌ 500 (unknown id, see the notes)
>
> ```json theme={null}
> { "message": "No query results for model [App\\Models\\Classroom] 99" }
> ```

**Notes**

* Newly added learners get the classroom slug in `classroom_joined_slugs`. Removed learners keep it.
* A **renamed slug** is replaced in every user's `classroom_joined_slugs` / `classroom_admin_slugs`. The classroom's chatroom is **not** renamed here: the apps send the new slug to [`PUT /chatrooms/{old slug}`](https://theteam-13fc7f49.mintlify.site/api-reference/chatrooms#3-4-update-a-chatroom-title-members-slug) right after this call.
* No notification is sent on update.

***

## 3.6 Archive a Classroom

`POST /classrooms/{id}/archive`

Hides the classroom from the default lists (`archived_at` is set). It can be restored with the unarchive endpoint.

**Path parameters**

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `id` | integer | Yes | Id of an active classroom of the caller's company. |

**Example request**

```text theme={null}
POST {{base_url}}/api/classrooms/2/archive
Accept: application/json
Authorization: Bearer {{token}}
```

**Responses**

> [tab] 200
>
> ✅ 200 OK
>
> ```json theme={null}
> { "message": "Classroom archived successfully" }
> ```

> [tab] 404
>
> ⚠️ 404 Not Found (unknown id, another company, or already archived)
>
> ```json theme={null}
> { "message": "Classroom not found" }
> ```

***

## 3.7 Unarchive a Classroom

`POST /classrooms/{id}/unarchive`

Restores an archived classroom.

**Path parameters**

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `id` | integer | Yes | Id of an archived classroom of the caller's company. |

**Example request**

```text theme={null}
POST {{base_url}}/api/classrooms/2/unarchive
Accept: application/json
Authorization: Bearer {{token}}
```

**Responses**

> [tab] 200
>
> ✅ 200 OK
>
> ```json theme={null}
> { "message": "Classroom unarchived successfully" }
> ```

> [tab] 404
>
> ⚠️ 404 Not Found
>
> ```json theme={null}
> { "message": "Classroom not found or not archived" }
> ```

***

## 3.8 Permanently Delete a Classroom

`DELETE /classroom/{id}/force_delete`

This is a real, permanent delete: the classroom row and its learner links are removed and cannot be restored. **Since 20/10/2026 the classroom's chatroom goes with it** (same slug, same company): its members, messages and images on R2 are deleted too, so the slug is free for a classroom created later with the same title.

The classroom slug is also removed from every user's `classroom_joined_slugs` and `classroom_admin_slugs`. The lookup sees archived classrooms (the admin UI only offers Delete on archived ones) and, for non-super-admins, only the caller's company.

**Path parameters**

| Name | Type | Required | Description |
| :- | :- | :- | :- |
| `id` | integer | Yes | Classroom id. |

**Example request**

```text theme={null}
DELETE {{base_url}}/api/classroom/2/force_delete
Accept: application/json
Authorization: Bearer {{token}}
```

**Responses**

> [tab] 200
>
> ✅ 200 OK
>
> ```json theme={null}
> { "message": "Classroom permanently deleted" }
> ```

> [tab] 404
>
> ⚠️ 404 Not Found
>
> ```json theme={null}
> { "message": "Classroom not found" }
> ```

***

## 4. Known Implementation Notes

* **Validation errors on create and update are a `422` since 20/10/2026** (they used to be a `500` with the first message, because the validation ran inside a `try` that caught every exception). The other write endpoints have no validation.
* **An archived classroom can be permanently deleted since 20/10/2026** (the lookup used to skip archived rows and answer `404`, so the admin UI's Delete never worked). The delete also removes the classroom's chatroom.
* **Classroom and chatroom slugs are independent columns** (`classrooms.classroom_slug`, `chatrooms.slug`), each unique in its own table and never checked against the other. A classroom and its chatroom are meant to share the value: creating "PMP Class 1" gives `classroom_slug = pmp-class-1`, and `POST /chatrooms` for it (with that `slug`, or with none and the same `title`) gives `chatrooms.slug = pmp-class-1`.
* **`content` is only needed when a classroom is opened.** `GET /classrooms/by-student` leaves it out; `GET /classrooms` still returns it.
* **Hidden users.** Archived and deleted users never appear in `users`, `user_ids` or the counts. A classroom whose instructor is archived or deleted returns `"instructor": null`.

***

## 5. Postman Setup

**Environment variables**

| Variable | Example |
| :- | :- |
| `base_url` | `http://127.0.0.1:8020` |
| `token` | A Sanctum token from `POST /api/auth/login` |

**Collection headers**

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

Every body is raw JSON.

**Smoke-test flow**

1. Log in as an admin and create a classroom with two learners (3.4).
2. List the classrooms (3.1): the row has `user_ids`, `users_count: 2`, `members_count: 3`, and an `instructor` with `email`.
3. Open it (3.2): `users` has both learners, each with `email`.
4. Log in as one of the learners and call 3.3 with your own id: no `content`, no `email` anywhere.
5. Still as the learner, call 3.3 with another student's id: `403`.
6. Still as the learner, open a classroom you are not part of (3.2): `403`.
7. Back as the admin: archive (3.6), unarchive (3.7), then delete (3.8).


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