# Curriculum & Video

Course sections and lessons, lesson video links and their playback tokens, per-student course access grants, and progress tracking.

Generated from `apps/api/openapi.json`. HTML version: https://docs.institflow.com/en/reference/curriculum

61 operations.

## PATCH /api/v1/assignments/{lessonId}

Update an assignment’s own fields (instructions, due date, scoring, submission kinds). Lesson-shared fields (title, section, drip gate) go through `PATCH /lessons/:id`.

- Operation id: `AssignmentsController_update_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `instructionsMd` | enum: "" \| string \| null | no |  |
| `dueAt` | string (date-time) \| null | no |  |
| `maxScore` | number | no | 0–1000 |
| `allowLate` | boolean | no |  |
| `submissionKinds` | array<enum: "text" \| "file" \| "link"> | no |  |

```json
{
  "instructionsMd": "",
  "dueAt": "2026-03-02T09:00:00.000Z",
  "maxScore": 0,
  "allowLate": true,
  "submissionKinds": [
    "text"
  ]
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `sectionId` | string (uuid) | yes |  |
| `title` | string | yes | ≥1 chars |
| `summary` | string \| null | yes | ≥1 chars |
| `contentMd` | string \| null | yes | ≥1 chars |
| `kind` | enum: "lesson" \| "assignment" \| "quiz" | yes |  |
| `status` | enum: "draft" \| "published" | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `availableFrom` | string (date-time) \| null | yes |  |
| `availableAfterDays` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `estimatedMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `video` | unknown \| null | yes |  |
| `assignment` | unknown \| null | yes |  |
| `quiz` | unknown \| null | yes |  |
| `audience` | LessonAudience | yes |  |
| `reminderMode` | enum: "inherit" \| "on" \| "off" | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PATCH "https://your-institute.example/api/v1/assignments/3d477cf4-3d47-7d47-8477-3d477cf43d47" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "instructionsMd": "",
    "dueAt": "2026-03-02T09:00:00.000Z",
    "maxScore": 0,
    "allowLate": true,
    "submissionKinds": [
      "text"
    ]
  }'
```

## POST /api/v1/assignments/{lessonId}/publish-to-gradebook

Publish every currently-graded submission into the gradebook as a `homework` exam per group (ق-33) — one click, upserts on repeat.

- Operation id: `AssignmentSubmissionsController_publishToGradebook_v1`
- Permission: `curriculum.assignments.grade`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `published` | array<object> | yes |  |
| `skipped` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/assignments/3d477cf4-3d47-7d47-8477-3d477cf43d47/publish-to-gradebook" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/assignments/{lessonId}/submissions

List an assignment’s submissions (cursor-paginated, optional status filter).

- Operation id: `AssignmentSubmissionsController_list_v1`
- Permission: `curriculum.assignments.grade`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |
| `status` | query | enum: "submitted" \| "late" \| "returned" \| "graded" | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/assignments/3d477cf4-3d47-7d47-8477-3d477cf43d47/submissions" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/assignments/{lessonId}/submissions/{submissionId}/grade

Grade a submission (score ≤ the assignment’s max score, optional feedback). Emits `assignment.graded`.

- Operation id: `AssignmentSubmissionsController_grade_v1`
- Permission: `curriculum.assignments.grade`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `submissionId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `score` | number | yes | ≥0 |
| `feedbackMd` | enum: "" \| string \| null | no |  |

```json
{
  "score": 0,
  "feedbackMd": ""
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `assignmentId` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `studentId` | string (uuid) | yes |  |
| `submittedAt` | string (date-time) | yes |  |
| `textMd` | string \| null | yes | ≥1 chars |
| `link` | string \| null | yes |  |
| `files` | array<SubmissionFile> | yes |  |
| `status` | enum: "submitted" \| "late" \| "returned" \| "graded" | yes |  |
| `score` | number \| null | yes |  |
| `maxScore` | number | yes |  |
| `feedbackMd` | string \| null | yes | ≥1 chars |
| `gradedByUserId` | string (uuid) \| null | yes |  |
| `gradedByName` | string \| null | yes | ≥1 chars |
| `gradedAt` | string (date-time) \| null | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/assignments/3d477cf4-3d47-7d47-8477-3d477cf43d47/submissions/a396314c-a396-7396-8963-a396314ca396/grade" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "score": 0,
    "feedbackMd": ""
  }'
```

## POST /api/v1/assignments/{lessonId}/submissions/{submissionId}/return

Send a submission back for revision (clears any earlier grade).

- Operation id: `AssignmentSubmissionsController_returnForRevision_v1`
- Permission: `curriculum.assignments.grade`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `submissionId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `feedbackMd` | enum: "" \| string \| null | no |  |

```json
{
  "feedbackMd": ""
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `assignmentId` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `studentId` | string (uuid) | yes |  |
| `submittedAt` | string (date-time) | yes |  |
| `textMd` | string \| null | yes | ≥1 chars |
| `link` | string \| null | yes |  |
| `files` | array<SubmissionFile> | yes |  |
| `status` | enum: "submitted" \| "late" \| "returned" \| "graded" | yes |  |
| `score` | number \| null | yes |  |
| `maxScore` | number | yes |  |
| `feedbackMd` | string \| null | yes | ≥1 chars |
| `gradedByUserId` | string (uuid) \| null | yes |  |
| `gradedByName` | string \| null | yes | ≥1 chars |
| `gradedAt` | string (date-time) \| null | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/assignments/3d477cf4-3d47-7d47-8477-3d477cf43d47/submissions/a396314c-a396-7396-8963-a396314ca396/return" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "feedbackMd": ""
  }'
```

## DELETE /api/v1/course-enrollments/{id}

Revoke a self-paced course grant. Emits `course_enrollment.dropped`.

- Operation id: `CourseEnrollmentsController_drop_v1`
- Permission: `enrollments.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |
| `reason` | query | string | no | 1–500 chars |

### Responses

**204** — The course enrollment was dropped.

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X DELETE "https://your-institute.example/api/v1/course-enrollments/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/courses/{courseId}/assignments

Create an assignment unit: a lesson of kind "assignment" plus its instructions/due-date/scoring, in one transaction.

- Operation id: `AssignmentsController_create_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `sectionId` | string (uuid) | no |  |
| `title` | string | yes | 1–200 chars |
| `summary` | enum: "" \| string \| null | no |  |
| `availableFrom` | string (date-time) \| null | no |  |
| `availableAfterDays` | integer \| null | no | 0–365 |
| `estimatedMinutes` | integer \| null | no | 1–600 |
| `instructionsMd` | enum: "" \| string \| null | no |  |
| `dueAt` | string (date-time) \| null | no |  |
| `maxScore` | number | yes | 0–1000 |
| `allowLate` | boolean | no | default: false |
| `submissionKinds` | array<enum: "text" \| "file" \| "link"> | yes |  |
| `audienceGroupIds` | array<string (uuid)> \| null | no |  |

```json
{
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": 0,
  "estimatedMinutes": 1,
  "instructionsMd": "",
  "dueAt": "2026-03-02T09:00:00.000Z",
  "maxScore": 0,
  "allowLate": false,
  "submissionKinds": [
    "text"
  ],
  "audienceGroupIds": [
    "1efa96cc-1efa-7efa-8fa9-1efa96cc1efa"
  ]
}
```

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `sectionId` | string (uuid) | yes |  |
| `title` | string | yes | ≥1 chars |
| `summary` | string \| null | yes | ≥1 chars |
| `contentMd` | string \| null | yes | ≥1 chars |
| `kind` | enum: "lesson" \| "assignment" \| "quiz" | yes |  |
| `status` | enum: "draft" \| "published" | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `availableFrom` | string (date-time) \| null | yes |  |
| `availableAfterDays` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `estimatedMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `video` | unknown \| null | yes |  |
| `assignment` | unknown \| null | yes |  |
| `quiz` | unknown \| null | yes |  |
| `audience` | LessonAudience | yes |  |
| `reminderMode` | enum: "inherit" \| "on" \| "off" | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/assignments" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
    "title": "Midterm Exam",
    "summary": "",
    "availableFrom": "2026-03-02T09:00:00.000Z",
    "availableAfterDays": 0,
    "estimatedMinutes": 1,
    "instructionsMd": "",
    "dueAt": "2026-03-02T09:00:00.000Z",
    "maxScore": 0,
    "allowLate": false,
    "submissionKinds": [
      "text"
    ],
    "audienceGroupIds": [
      "1efa96cc-1efa-7efa-8fa9-1efa96cc1efa"
    ]
  }'
```

## GET /api/v1/courses/{courseId}/audience-groups

List the groups a unit of this course may be restricted to (id and name only, cursor-paginated) — for the «لمن هذا الدرس؟» selector; needs `curriculum.write` or `curriculum.quizzes.manage` (whoever may set an audience), not the groups permission.

- Operation id: `LessonAudienceController_list_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/audience-groups" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/courses/{courseId}/curriculum/summary

Authoring dashboard tile: section/lesson/published/video counts for one course.

- Operation id: `CurriculumController_getSummary_v1`
- Permission: `curriculum.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `courseId` | string (uuid) | yes |  |
| `sectionCount` | integer | yes | 0–9007199254740991 |
| `lessonCount` | integer | yes | 0–9007199254740991 |
| `publishedLessonCount` | integer | yes | 0–9007199254740991 |
| `videoLessonCount` | integer | yes | 0–9007199254740991 |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/curriculum/summary" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/courses/{courseId}/enrollments

List a course’s self-paced enrollments (cursor-paginated, optional status filter).

- Operation id: `CourseEnrollmentsController_listForCourse_v1`
- Permission: `enrollments.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |
| `status` | query | enum: "pending" \| "active" \| "completed" \| "dropped" | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/enrollments" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/courses/{courseId}/enrollments

Grant a student access to a self-paced course. Idempotent, price snapshotted from the course fee, emits `course_enrollment.created`.

- Operation id: `CourseEnrollmentsController_create_v1`
- Permission: `enrollments.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `studentId` | string (uuid) | yes |  |
| `priceMinor` | integer | no | 0–9007199254740991 |
| `currency` | enum: "ILS" \| "USD" \| "EUR" | no |  |

```json
{
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "priceMinor": 12000,
  "currency": "ILS"
}
```

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `studentId` | string (uuid) | yes |  |
| `status` | enum: "pending" \| "active" \| "completed" \| "dropped" | yes |  |
| `grantedAt` | string (date-time) | yes |  |
| `endedAt` | string (date-time) \| null | yes |  |
| `priceMinor` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `currency` | enum: "ILS" \| "USD" \| "EUR" \| null \| null | yes |  |
| `grantedByUserId` | string (uuid) \| null | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/enrollments" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
    "priceMinor": 12000,
    "currency": "ILS"
  }'
```

## GET /api/v1/courses/{courseId}/lessons

List a course’s lessons (cursor-paginated, optional section/status filter).

- Operation id: `LessonsController_list_v1`
- Permission: `curriculum.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |
| `sectionId` | query | string (uuid) | no |  |
| `status` | query | enum: "draft" \| "published" | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/lessons" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/courses/{courseId}/lessons

Create a lesson. Omit `sectionId` to use/auto-create the course’s default section.

- Operation id: `LessonsController_create_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `sectionId` | string (uuid) | no |  |
| `title` | string | yes | 1–200 chars |
| `summary` | enum: "" \| string \| null | no |  |
| `contentMd` | enum: "" \| string \| null | no |  |
| `availableFrom` | string (date-time) \| null | no |  |
| `availableAfterDays` | integer \| null | no | 0–365 |
| `estimatedMinutes` | integer \| null | no | 1–600 |
| `audienceGroupIds` | array<string (uuid)> \| null | no |  |

```json
{
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "",
  "contentMd": "",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": 0,
  "estimatedMinutes": 1,
  "audienceGroupIds": [
    "1efa96cc-1efa-7efa-8fa9-1efa96cc1efa"
  ]
}
```

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `sectionId` | string (uuid) | yes |  |
| `title` | string | yes | ≥1 chars |
| `summary` | string \| null | yes | ≥1 chars |
| `contentMd` | string \| null | yes | ≥1 chars |
| `kind` | enum: "lesson" \| "assignment" \| "quiz" | yes |  |
| `status` | enum: "draft" \| "published" | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `availableFrom` | string (date-time) \| null | yes |  |
| `availableAfterDays` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `estimatedMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `video` | unknown \| null | yes |  |
| `assignment` | unknown \| null | yes |  |
| `quiz` | unknown \| null | yes |  |
| `audience` | LessonAudience | yes |  |
| `reminderMode` | enum: "inherit" \| "on" \| "off" | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/lessons" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
    "title": "Midterm Exam",
    "summary": "",
    "contentMd": "",
    "availableFrom": "2026-03-02T09:00:00.000Z",
    "availableAfterDays": 0,
    "estimatedMinutes": 1,
    "audienceGroupIds": [
      "1efa96cc-1efa-7efa-8fa9-1efa96cc1efa"
    ]
  }'
```

## GET /api/v1/courses/{courseId}/progress

Teacher progress matrix: published units × students who may reach the course, cursor-paginated on students (max 100).

- Operation id: `ProgressViewsController_getMatrix_v1`
- Permission: `curriculum.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |
| `search` | query | string | no | 1–200 chars |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `courseId` | string (uuid) | yes |  |
| `units` | array<ProgressMatrixUnit> | yes |  |
| `students` | array<ProgressMatrixStudent> | yes |  |
| `cells` | object | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/progress" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/courses/{courseId}/progress/students/{studentId}

One student's unit-by-unit progress for a course, row-scoped like the matrix.

- Operation id: `ProgressViewsController_getStudentProgress_v1`
- Permission: `curriculum.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |
| `studentId` | path | string | yes |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `courseId` | string (uuid) | yes |  |
| `studentId` | string (uuid) | yes |  |
| `fullName` | string | yes | ≥1 chars |
| `completedCount` | integer | yes | 0–9007199254740991 |
| `percent` | integer | yes | 0–100 |
| `units` | array<StudentProgressUnit> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/progress/students/a4a70332-a4a7-74a7-8a70-a4a70332a4a7" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/courses/{courseId}/quizzes

Create a quiz unit: a lesson of kind "quiz" plus its settings, in one transaction. No questions yet.

- Operation id: `QuizzesController_create_v1`
- Permission: `curriculum.quizzes.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `sectionId` | string (uuid) | no |  |
| `title` | string | yes | 1–200 chars |
| `summary` | enum: "" \| string \| null | no |  |
| `availableFrom` | string (date-time) \| null | no |  |
| `availableAfterDays` | integer \| null | no | 0–365 |
| `estimatedMinutes` | integer \| null | no | 1–600 |
| `timeLimitMinutes` | integer \| null | no | 1–600 |
| `attemptsAllowed` | integer | no | 1–20 · default: 1 |
| `shuffle` | boolean | no | default: false |
| `showAnswersAfter` | enum: "never" \| "after_submit" \| "after_due" | no | default: "after_submit" |
| `passScore` | number \| null | no | ≤100000 |
| `dueAt` | string (date-time) \| null | no |  |
| `audienceGroupIds` | array<string (uuid)> \| null | no |  |

```json
{
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": 0,
  "estimatedMinutes": 1,
  "timeLimitMinutes": 1,
  "attemptsAllowed": 1,
  "shuffle": false,
  "showAnswersAfter": "after_submit",
  "passScore": 1,
  "dueAt": "2026-03-02T09:00:00.000Z",
  "audienceGroupIds": [
    "1efa96cc-1efa-7efa-8fa9-1efa96cc1efa"
  ]
}
```

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `sectionId` | string (uuid) | yes |  |
| `title` | string | yes | ≥1 chars |
| `summary` | string \| null | yes | ≥1 chars |
| `contentMd` | string \| null | yes | ≥1 chars |
| `kind` | enum: "lesson" \| "assignment" \| "quiz" | yes |  |
| `status` | enum: "draft" \| "published" | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `availableFrom` | string (date-time) \| null | yes |  |
| `availableAfterDays` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `estimatedMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `video` | unknown \| null | yes |  |
| `assignment` | unknown \| null | yes |  |
| `quiz` | unknown \| null | yes |  |
| `audience` | LessonAudience | yes |  |
| `reminderMode` | enum: "inherit" \| "on" \| "off" | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/quizzes" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
    "title": "Midterm Exam",
    "summary": "",
    "availableFrom": "2026-03-02T09:00:00.000Z",
    "availableAfterDays": 0,
    "estimatedMinutes": 1,
    "timeLimitMinutes": 1,
    "attemptsAllowed": 1,
    "shuffle": false,
    "showAnswersAfter": "after_submit",
    "passScore": 1,
    "dueAt": "2026-03-02T09:00:00.000Z",
    "audienceGroupIds": [
      "1efa96cc-1efa-7efa-8fa9-1efa96cc1efa"
    ]
  }'
```

## GET /api/v1/courses/{courseId}/sections

List a course’s sections (cursor-paginated, authoring order).

- Operation id: `SectionsController_list_v1`
- Permission: `curriculum.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/sections" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/courses/{courseId}/sections

Create a section.

- Operation id: `SectionsController_create_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `title` | string | yes | 1–200 chars |
| `description` | string \| string | no |  |

```json
{
  "title": "Midterm Exam",
  "description": "A short, human-readable description."
}
```

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `title` | string | yes | ≥1 chars |
| `description` | string \| null | yes | ≥1 chars |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/sections" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "title": "Midterm Exam",
    "description": "A short, human-readable description."
  }'
```

## PUT /api/v1/courses/{courseId}/sections/order

Reorder every section of a course in one call (whole-list, unpaginated response envelope).

- Operation id: `SectionsController_reorder_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `sectionIds` | array<string (uuid)> | yes |  |

```json
{
  "sectionIds": [
    "72a7f6d6-72a7-72a7-8a7f-72a7f6d672a7"
  ]
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/sections/order" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "sectionIds": [
      "72a7f6d6-72a7-72a7-8a7f-72a7f6d672a7"
    ]
  }'
```

## GET /api/v1/curriculum/usage

This tenant’s plan usage for the curriculum feature (quota meter).

- Operation id: `CurriculumController_getUsage_v1`
- Permission: `curriculum.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `videoCourses` | object | yes |  |
| `lessonsPerCourse` | object | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/curriculum/usage" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## DELETE /api/v1/lesson-files/{id}

Delete a lesson file: removes the row, then the stored object. Audited.

- Operation id: `LessonFilesController_remove_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Responses

**204** — The file was deleted.

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X DELETE "https://your-institute.example/api/v1/lesson-files/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PATCH /api/v1/lesson-files/{id}

Rename a lesson file (it keeps its extension), change its description or who may see it.

- Operation id: `LessonFilesController_update_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `originalName` | string | no | 1–255 chars |
| `description` | enum: "" \| string \| null | no |  |
| `visibility` | enum: "learners" \| "staff" | no |  |

```json
{
  "originalName": "string",
  "description": "",
  "visibility": "learners"
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `originalName` | string | yes | ≥1 chars |
| `mimeType` | string | yes | ≥1 chars |
| `sizeBytes` | integer | yes | 0–9007199254740991 |
| `description` | string \| null | yes | ≥1 chars |
| `visibility` | enum: "learners" \| "staff" | yes |  |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `createdAt` | string (date-time) | yes |  |
| `downloadUrl` | string | yes |  |
| `downloadUrlExpiresAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PATCH "https://your-institute.example/api/v1/lesson-files/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "originalName": "string",
    "description": "",
    "visibility": "learners"
  }'
```

## DELETE /api/v1/lessons/{id}

Delete a lesson (cascades its video and progress rows).

- Operation id: `LessonsController_remove_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Responses

**204** — The lesson was deleted.

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X DELETE "https://your-institute.example/api/v1/lessons/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/lessons/{id}

Get one lesson (author view — draft content and video included).

- Operation id: `LessonsController_getById_v1`
- Permission: `curriculum.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `sectionId` | string (uuid) | yes |  |
| `title` | string | yes | ≥1 chars |
| `summary` | string \| null | yes | ≥1 chars |
| `contentMd` | string \| null | yes | ≥1 chars |
| `kind` | enum: "lesson" \| "assignment" \| "quiz" | yes |  |
| `status` | enum: "draft" \| "published" | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `availableFrom` | string (date-time) \| null | yes |  |
| `availableAfterDays` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `estimatedMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `video` | unknown \| null | yes |  |
| `assignment` | unknown \| null | yes |  |
| `quiz` | unknown \| null | yes |  |
| `audience` | LessonAudience | yes |  |
| `reminderMode` | enum: "inherit" \| "on" \| "off" | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/lessons/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PATCH /api/v1/lessons/{id}

Update a lesson.

- Operation id: `LessonsController_update_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `sectionId` | string (uuid) | no |  |
| `title` | string | no | 1–200 chars |
| `summary` | enum: "" \| string \| null | no |  |
| `contentMd` | enum: "" \| string \| null | no |  |
| `availableFrom` | string (date-time) \| null | no |  |
| `availableAfterDays` | integer \| null | no | 0–365 |
| `estimatedMinutes` | integer \| null | no | 1–600 |
| `audienceGroupIds` | array<string (uuid)> \| null | no |  |
| `reminderMode` | enum: "inherit" \| "on" \| "off" | no |  |

```json
{
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "",
  "contentMd": "",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": 0,
  "estimatedMinutes": 1,
  "audienceGroupIds": [
    "1efa96cc-1efa-7efa-8fa9-1efa96cc1efa"
  ],
  "reminderMode": "inherit"
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `sectionId` | string (uuid) | yes |  |
| `title` | string | yes | ≥1 chars |
| `summary` | string \| null | yes | ≥1 chars |
| `contentMd` | string \| null | yes | ≥1 chars |
| `kind` | enum: "lesson" \| "assignment" \| "quiz" | yes |  |
| `status` | enum: "draft" \| "published" | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `availableFrom` | string (date-time) \| null | yes |  |
| `availableAfterDays` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `estimatedMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `video` | unknown \| null | yes |  |
| `assignment` | unknown \| null | yes |  |
| `quiz` | unknown \| null | yes |  |
| `audience` | LessonAudience | yes |  |
| `reminderMode` | enum: "inherit" \| "on" \| "off" | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PATCH "https://your-institute.example/api/v1/lessons/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
    "title": "Midterm Exam",
    "summary": "",
    "contentMd": "",
    "availableFrom": "2026-03-02T09:00:00.000Z",
    "availableAfterDays": 0,
    "estimatedMinutes": 1,
    "audienceGroupIds": [
      "1efa96cc-1efa-7efa-8fa9-1efa96cc1efa"
    ],
    "reminderMode": "inherit"
  }'
```

## POST /api/v1/lessons/{id}/publish

Publish a lesson (emits `lesson.published`). Refused if it has neither content nor a video.

- Operation id: `LessonsController_publish_v1`
- Permission: `curriculum.publish`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `sectionId` | string (uuid) | yes |  |
| `title` | string | yes | ≥1 chars |
| `summary` | string \| null | yes | ≥1 chars |
| `contentMd` | string \| null | yes | ≥1 chars |
| `kind` | enum: "lesson" \| "assignment" \| "quiz" | yes |  |
| `status` | enum: "draft" \| "published" | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `availableFrom` | string (date-time) \| null | yes |  |
| `availableAfterDays` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `estimatedMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `video` | unknown \| null | yes |  |
| `assignment` | unknown \| null | yes |  |
| `quiz` | unknown \| null | yes |  |
| `audience` | LessonAudience | yes |  |
| `reminderMode` | enum: "inherit" \| "on" \| "off" | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/lessons/37386ae0-3738-7738-8386-37386ae03738/publish" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/lessons/{id}/unpublish

Unpublish a lesson back to draft.

- Operation id: `LessonsController_unpublish_v1`
- Permission: `curriculum.publish`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `sectionId` | string (uuid) | yes |  |
| `title` | string | yes | ≥1 chars |
| `summary` | string \| null | yes | ≥1 chars |
| `contentMd` | string \| null | yes | ≥1 chars |
| `kind` | enum: "lesson" \| "assignment" \| "quiz" | yes |  |
| `status` | enum: "draft" \| "published" | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `availableFrom` | string (date-time) \| null | yes |  |
| `availableAfterDays` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `estimatedMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `video` | unknown \| null | yes |  |
| `assignment` | unknown \| null | yes |  |
| `quiz` | unknown \| null | yes |  |
| `audience` | LessonAudience | yes |  |
| `reminderMode` | enum: "inherit" \| "on" \| "off" | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/lessons/37386ae0-3738-7738-8386-37386ae03738/unpublish" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## DELETE /api/v1/lessons/{id}/video

Remove a lesson’s video (idempotent).

- Operation id: `LessonVideosController_remove_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Responses

**204** — The video was removed (or was already absent).

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X DELETE "https://your-institute.example/api/v1/lessons/37386ae0-3738-7738-8386-37386ae03738/video" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PUT /api/v1/lessons/{id}/video

Set (or replace) a lesson’s video source. Only the parsed (provider, externalRef) is ever stored — never the raw URL.

- Operation id: `LessonVideosController_setVideo_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `url` | string | yes | 1–2048 chars |
| `title` | string | no | 1–200 chars |
| `durationSeconds` | integer | no | 1–86400 |

```json
{
  "url": "https://example.com/webhooks/institflow",
  "title": "Midterm Exam",
  "durationSeconds": 1
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `provider` | enum: "youtube" | yes |  |
| `externalRef` | string | yes | ≥1 chars |
| `canonicalUrl` | string | yes |  |
| `embedUrl` | string | yes |  |
| `title` | string \| null | yes | ≥1 chars |
| `thumbnailUrl` | string \| null | yes |  |
| `durationSeconds` | integer \| null | yes | 1–9007199254740991 |
| `verifiedAt` | string (date-time) \| null | yes |  |
| `capabilities` | object | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/lessons/37386ae0-3738-7738-8386-37386ae03738/video" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "url": "https://example.com/webhooks/institflow",
    "title": "Midterm Exam",
    "durationSeconds": 1
  }'
```

## POST /api/v1/lessons/{id}/video/verify

Stamp `verifiedAt` — the author confirms the video actually plays.

- Operation id: `LessonVideosController_verify_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `provider` | enum: "youtube" | yes |  |
| `externalRef` | string | yes | ≥1 chars |
| `canonicalUrl` | string | yes |  |
| `embedUrl` | string | yes |  |
| `title` | string \| null | yes | ≥1 chars |
| `thumbnailUrl` | string \| null | yes |  |
| `durationSeconds` | integer \| null | yes | 1–9007199254740991 |
| `verifiedAt` | string (date-time) \| null | yes |  |
| `capabilities` | object | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/lessons/37386ae0-3738-7738-8386-37386ae03738/video/verify" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/lessons/{lessonId}/files

List a lesson’s files for authors and teachers: every visibility, with a short-lived signed download URL each (whole list, `nextCursor` is always null).

- Operation id: `LessonFilesController_list_v1`
- Permission: `curriculum.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<LessonFile> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/files" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/lessons/{lessonId}/files

Register an uploaded lesson file. The stored object’s first bytes are checked against its type; a mismatch deletes the object and refuses the file.

- Operation id: `LessonFilesController_complete_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `originalName` | string | yes | 1–255 chars |
| `mimeType` | string | yes | 1–127 chars |
| `sizeBytes` | integer | yes | 1–20971520 |
| `fileId` | string (uuid) | no |  |
| `storageKey` | string | yes | 1–1024 chars |
| `sha256` | string | yes |  |
| `description` | string \| string | no |  |
| `visibility` | enum: "learners" \| "staff" | no |  |

```json
{
  "originalName": "string",
  "mimeType": "string",
  "sizeBytes": 1,
  "fileId": "0daca32e-0dac-7dac-8aca-0daca32e0dac",
  "storageKey": "string",
  "sha256": "string",
  "description": "A short, human-readable description.",
  "visibility": "learners"
}
```

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `originalName` | string | yes | ≥1 chars |
| `mimeType` | string | yes | ≥1 chars |
| `sizeBytes` | integer | yes | 0–9007199254740991 |
| `description` | string \| null | yes | ≥1 chars |
| `visibility` | enum: "learners" \| "staff" | yes |  |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `createdAt` | string (date-time) | yes |  |
| `downloadUrl` | string | yes |  |
| `downloadUrlExpiresAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/files" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "originalName": "string",
    "mimeType": "string",
    "sizeBytes": 1,
    "fileId": "0daca32e-0dac-7dac-8aca-0daca32e0dac",
    "storageKey": "string",
    "sha256": "string",
    "description": "A short, human-readable description.",
    "visibility": "learners"
  }'
```

## PUT /api/v1/lessons/{lessonId}/files/order

Reorder every file of a lesson in one call (whole list, `nextCursor` is always null).

- Operation id: `LessonFilesController_reorder_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `fileIds` | array<string (uuid)> | yes |  |

```json
{
  "fileIds": [
    "e3c52b67-e3c5-73c5-8c52-e3c52b67e3c5"
  ]
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<LessonFile> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/files/order" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "fileIds": [
      "e3c52b67-e3c5-73c5-8c52-e3c52b67e3c5"
    ]
  }'
```

## POST /api/v1/lessons/{lessonId}/files/upload-url

Get a signed URL to upload a lesson file (PUT the object to `uploadUrl` with the same `Content-Type`). Refused up front for a type that is not allowed, a file over 20 MiB, or when the server has no space left (`STORAGE_FULL`).

- Operation id: `LessonFilesController_createUploadUrl_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `originalName` | string | yes | 1–255 chars |
| `mimeType` | string | yes | 1–127 chars |
| `sizeBytes` | integer | yes | 1–20971520 |

```json
{
  "originalName": "string",
  "mimeType": "string",
  "sizeBytes": 1
}
```

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `storageKey` | string | yes |  |
| `uploadUrl` | string | yes |  |
| `expiresAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/files/upload-url" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "originalName": "string",
    "mimeType": "string",
    "sizeBytes": 1
  }'
```

## DELETE /api/v1/lessons/{lessonId}/task

Remove the task under a video lesson. A task that has submissions is refused with 409 (carrying the counts) until `confirmDeleteSubmissions=true` is sent; graded work is never deleted silently.

- Operation id: `LessonTasksController_remove_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `confirmDeleteSubmissions` | query | enum: "true" \| "false" | no |  |

### Responses

**204** — The task was removed.

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X DELETE "https://your-institute.example/api/v1/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/task" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PUT /api/v1/lessons/{lessonId}/task

Create or replace the task under a video lesson (instructions, due date, max score, accepted submission kinds). Refused for assignment and quiz units.

- Operation id: `LessonTasksController_save_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `instructionsMd` | string | yes | 1–20000 chars |
| `dueAt` | string (date-time) \| null | no |  |
| `maxScore` | number | yes | 0–1000 |
| `allowLate` | boolean | no | default: false |
| `submissionKinds` | array<enum: "text" \| "file" \| "link"> | yes |  |

```json
{
  "instructionsMd": "string",
  "dueAt": "2026-03-02T09:00:00.000Z",
  "maxScore": 0,
  "allowLate": false,
  "submissionKinds": [
    "text"
  ]
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `sectionId` | string (uuid) | yes |  |
| `title` | string | yes | ≥1 chars |
| `summary` | string \| null | yes | ≥1 chars |
| `contentMd` | string \| null | yes | ≥1 chars |
| `kind` | enum: "lesson" \| "assignment" \| "quiz" | yes |  |
| `status` | enum: "draft" \| "published" | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `availableFrom` | string (date-time) \| null | yes |  |
| `availableAfterDays` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `estimatedMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `video` | unknown \| null | yes |  |
| `assignment` | unknown \| null | yes |  |
| `quiz` | unknown \| null | yes |  |
| `audience` | LessonAudience | yes |  |
| `reminderMode` | enum: "inherit" \| "on" \| "off" | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/task" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "instructionsMd": "string",
    "dueAt": "2026-03-02T09:00:00.000Z",
    "maxScore": 0,
    "allowLate": false,
    "submissionKinds": [
      "text"
    ]
  }'
```

## GET /api/v1/me/courses

Own courses with progress (student), or a linked child’s via `?studentId` (guardian). Published, unlocked lessons only.

- Operation id: `MeCurriculumController_listCourses_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |
| `studentId` | query | string (uuid) | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<MyCourseResponse> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/me/courses" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/me/courses/{courseId}/lessons

Published lessons of one accessible course, in presentation order. A locked lesson is listed but its body, video and attachments are withheld.

- Operation id: `MeCurriculumController_listLessons_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `courseId` | path | string | yes |  |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |
| `studentId` | query | string (uuid) | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<MyLessonListItem> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/me/courses/d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4/lessons" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/me/lessons/{lessonId}

One published, accessible lesson: body, video embed, attachments and its neighbours.

- Operation id: `MeCurriculumController_getLesson_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `studentId` | query | string (uuid) | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `sectionId` | string (uuid) | yes |  |
| `sectionTitle` | string | yes | ≥1 chars |
| `title` | string | yes | ≥1 chars |
| `summary` | string \| null | yes | ≥1 chars |
| `kind` | enum: "lesson" \| "assignment" \| "quiz" | yes |  |
| `estimatedMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `hasVideo` | boolean | yes |  |
| `locked` | boolean | yes |  |
| `availableAt` | string (date-time) \| null | yes |  |
| `progress` | unknown \| null | yes |  |
| `contentMd` | string \| null | yes | ≥1 chars |
| `attachments` | array<MyLessonAttachment> | yes |  |
| `video` | unknown \| null | yes |  |
| `assignment` | unknown \| null | yes |  |
| `quiz` | unknown \| null | yes |  |
| `previousLessonId` | string (uuid) \| null | yes |  |
| `nextLessonId` | string (uuid) \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/me/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PUT /api/v1/me/lessons/{lessonId}/progress

Record lesson progress (self-reported, non-authoritative). Idempotent: `lesson.completed` is emitted once, on the transition only.

- Operation id: `LessonProgressController_upsert_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `studentId` | query | string (uuid) | no |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `positionSeconds` | integer | yes | 0–86400 |
| `watchedSeconds` | integer | no | 0–86400 |
| `completed` | boolean | no |  |

```json
{
  "positionSeconds": 0,
  "watchedSeconds": 0,
  "completed": true
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `lessonId` | string (uuid) | yes |  |
| `studentId` | string (uuid) | yes |  |
| `status` | enum: "in_progress" \| "completed" | yes |  |
| `lastPositionSeconds` | integer | yes | 0–9007199254740991 |
| `watchedSeconds` | integer | yes | 0–9007199254740991 |
| `firstStartedAt` | string (date-time) | yes |  |
| `lastSeenAt` | string (date-time) | yes |  |
| `completedAt` | string (date-time) \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/me/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/progress" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "positionSeconds": 0,
    "watchedSeconds": 0,
    "completed": true
  }'
```

## GET /api/v1/me/lessons/{lessonId}/quiz-attempts

My attempts at this quiz, most recent first, with scores; per-answer detail per `showAnswersAfter`.

- Operation id: `MeQuizAttemptsController_listMine_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `studentId` | query | string (uuid) | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/me/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/quiz-attempts" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/me/lessons/{lessonId}/quiz-attempts

Start a new attempt (or resume the current in-progress one). Refused when attempts are exhausted or the quiz is closed past its due date. Questions are returned without `correct`, shuffled per attempt when the quiz shuffles.

- Operation id: `MeQuizAttemptsController_start_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `studentId` | query | string (uuid) | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `quizId` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `studentId` | string (uuid) | yes |  |
| `attemptNumber` | integer | yes | -9007199254740991–9007199254740991 |
| `status` | enum: "in_progress" \| "submitted" \| "expired" | yes |  |
| `startedAt` | string (date-time) | yes |  |
| `submittedAt` | string (date-time) \| null | yes |  |
| `deadlineAt` | string (date-time) \| null | yes |  |
| `score` | number \| null | yes |  |
| `maxScore` | number | yes |  |
| `passed` | boolean \| null | yes |  |
| `questions` | array<QuizAttemptQuestion> | yes |  |
| `answers` | array<QuizAttemptAnswer> \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/me/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/quiz-attempts" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/me/lessons/{lessonId}/quiz-attempts/{attemptId}

Read one of my attempts.

- Operation id: `MeQuizAttemptsController_findMine_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `attemptId` | path | string | yes |  |
| `studentId` | query | string (uuid) | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `quizId` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `studentId` | string (uuid) | yes |  |
| `attemptNumber` | integer | yes | -9007199254740991–9007199254740991 |
| `status` | enum: "in_progress" \| "submitted" \| "expired" | yes |  |
| `startedAt` | string (date-time) | yes |  |
| `submittedAt` | string (date-time) \| null | yes |  |
| `deadlineAt` | string (date-time) \| null | yes |  |
| `score` | number \| null | yes |  |
| `maxScore` | number | yes |  |
| `passed` | boolean \| null | yes |  |
| `questions` | array<QuizAttemptQuestion> | yes |  |
| `answers` | array<QuizAttemptAnswer> \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/me/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/quiz-attempts/ca174553-ca17-7a17-8174-ca174553ca17" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PUT /api/v1/me/lessons/{lessonId}/quiz-attempts/{attemptId}/answers/{questionId}

Upsert one answer. Refused after submit or past the attempt’s deadline.

- Operation id: `MeQuizAttemptsController_answer_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `attemptId` | path | string | yes |  |
| `questionId` | path | string | yes |  |
| `studentId` | query | string (uuid) | no |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `answer` | string \| array<string> \| boolean \| null | yes |  |

```json
{
  "answer": "string"
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `quizId` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `studentId` | string (uuid) | yes |  |
| `attemptNumber` | integer | yes | -9007199254740991–9007199254740991 |
| `status` | enum: "in_progress" \| "submitted" \| "expired" | yes |  |
| `startedAt` | string (date-time) | yes |  |
| `submittedAt` | string (date-time) \| null | yes |  |
| `deadlineAt` | string (date-time) \| null | yes |  |
| `score` | number \| null | yes |  |
| `maxScore` | number | yes |  |
| `passed` | boolean \| null | yes |  |
| `questions` | array<QuizAttemptQuestion> | yes |  |
| `answers` | array<QuizAttemptAnswer> \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/me/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/quiz-attempts/ca174553-ca17-7a17-8174-ca174553ca17/answers/53cf0780-53cf-73cf-8cf0-53cf078053cf" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "answer": "string"
  }'
```

## POST /api/v1/me/lessons/{lessonId}/quiz-attempts/{attemptId}/submit

Submit the attempt: auto-grades every closed question, computes score/passed, and stamps lesson_progress completion per §3.6. Refused past the attempt’s deadline.

- Operation id: `MeQuizAttemptsController_submit_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `attemptId` | path | string | yes |  |
| `studentId` | query | string (uuid) | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `quizId` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `studentId` | string (uuid) | yes |  |
| `attemptNumber` | integer | yes | -9007199254740991–9007199254740991 |
| `status` | enum: "in_progress" \| "submitted" \| "expired" | yes |  |
| `startedAt` | string (date-time) | yes |  |
| `submittedAt` | string (date-time) \| null | yes |  |
| `deadlineAt` | string (date-time) \| null | yes |  |
| `score` | number \| null | yes |  |
| `maxScore` | number | yes |  |
| `passed` | boolean \| null | yes |  |
| `questions` | array<QuizAttemptQuestion> | yes |  |
| `answers` | array<QuizAttemptAnswer> \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/me/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/quiz-attempts/ca174553-ca17-7a17-8174-ca174553ca17/submit" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PUT /api/v1/me/lessons/{lessonId}/submission

Submit or resubmit an assignment (self-scoped upload for any attached files, `entityType: "assignment_submission"`, `entityId: <lessonId>`, before this call). Refused once graded until the teacher returns it.

- Operation id: `MeAssignmentSubmissionsController_submit_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `studentId` | query | string (uuid) | no |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `textMd` | enum: "" \| string \| null | no |  |
| `link` | enum: "" \| string \| null | no |  |

```json
{
  "textMd": "",
  "link": ""
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `assignmentId` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `studentId` | string (uuid) | yes |  |
| `submittedAt` | string (date-time) | yes |  |
| `textMd` | string \| null | yes | ≥1 chars |
| `link` | string \| null | yes |  |
| `files` | array<SubmissionFile> | yes |  |
| `status` | enum: "submitted" \| "late" \| "returned" \| "graded" | yes |  |
| `score` | number \| null | yes |  |
| `maxScore` | number | yes |  |
| `feedbackMd` | string \| null | yes | ≥1 chars |
| `gradedByUserId` | string (uuid) \| null | yes |  |
| `gradedByName` | string \| null | yes | ≥1 chars |
| `gradedAt` | string (date-time) \| null | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/me/lessons/3d477cf4-3d47-7d47-8477-3d477cf43d47/submission" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "textMd": "",
    "link": ""
  }'
```

## GET /api/v1/me/next-up

What to do next: due assignments (ascending due date, overdue flagged) then unstarted/in-progress units, capped at 10.

- Operation id: `MeNextUpController_getNextUp_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `studentId` | query | string (uuid) | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `studentId` | string (uuid) | yes |  |
| `items` | array<NextUpItem> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/me/next-up" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PATCH /api/v1/quizzes/{lessonId}

Update a quiz’s own settings (time limit, attempts allowed, shuffle, show-answers-after, pass score, due date). Lesson-shared fields go through `PATCH /lessons/:id`.

- Operation id: `QuizzesController_update_v1`
- Permission: `curriculum.quizzes.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `timeLimitMinutes` | integer \| null | no | 1–600 |
| `attemptsAllowed` | integer | no | 1–20 |
| `shuffle` | boolean | no |  |
| `showAnswersAfter` | enum: "never" \| "after_submit" \| "after_due" | no |  |
| `passScore` | number \| null | no | ≤100000 |
| `dueAt` | string (date-time) \| null | no |  |

```json
{
  "timeLimitMinutes": 1,
  "attemptsAllowed": 1,
  "shuffle": true,
  "showAnswersAfter": "never",
  "passScore": 1,
  "dueAt": "2026-03-02T09:00:00.000Z"
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `sectionId` | string (uuid) | yes |  |
| `title` | string | yes | ≥1 chars |
| `summary` | string \| null | yes | ≥1 chars |
| `contentMd` | string \| null | yes | ≥1 chars |
| `kind` | enum: "lesson" \| "assignment" \| "quiz" | yes |  |
| `status` | enum: "draft" \| "published" | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `availableFrom` | string (date-time) \| null | yes |  |
| `availableAfterDays` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `estimatedMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `video` | unknown \| null | yes |  |
| `assignment` | unknown \| null | yes |  |
| `quiz` | unknown \| null | yes |  |
| `audience` | LessonAudience | yes |  |
| `reminderMode` | enum: "inherit" \| "on" \| "off" | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PATCH "https://your-institute.example/api/v1/quizzes/3d477cf4-3d47-7d47-8477-3d477cf43d47" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "timeLimitMinutes": 1,
    "attemptsAllowed": 1,
    "shuffle": true,
    "showAnswersAfter": "never",
    "passScore": 1,
    "dueAt": "2026-03-02T09:00:00.000Z"
  }'
```

## GET /api/v1/quizzes/{lessonId}/attempts

List a quiz’s attempts (cursor-paginated, optional student/status filters).

- Operation id: `QuizAttemptsController_list_v1`
- Permission: `curriculum.assignments.grade`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |
| `studentId` | query | string (uuid) | no |  |
| `status` | query | enum: "in_progress" \| "submitted" | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/quizzes/3d477cf4-3d47-7d47-8477-3d477cf43d47/attempts" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/quizzes/{lessonId}/attempts/{attemptId}/answers/{questionId}/grade

Grade a short-answer (`short_text`) answer (points ≤ the question’s own points). Finalises the attempt’s score/passed once nothing is left ungraded. Emits `quiz.graded` on finalisation.

- Operation id: `QuizAttemptsController_gradeAnswer_v1`
- Permission: `curriculum.assignments.grade`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `attemptId` | path | string | yes |  |
| `questionId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `pointsAwarded` | number | yes | ≥0 |
| `feedbackMd` | enum: "" \| string \| null | no |  |

```json
{
  "pointsAwarded": 0,
  "feedbackMd": ""
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `quizId` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `studentId` | string (uuid) | yes |  |
| `attemptNumber` | integer | yes | -9007199254740991–9007199254740991 |
| `status` | enum: "in_progress" \| "submitted" \| "expired" | yes |  |
| `startedAt` | string (date-time) | yes |  |
| `submittedAt` | string (date-time) \| null | yes |  |
| `deadlineAt` | string (date-time) \| null | yes |  |
| `score` | number \| null | yes |  |
| `maxScore` | number | yes |  |
| `passed` | boolean \| null | yes |  |
| `questions` | array<QuizAttemptQuestion> | yes |  |
| `answers` | array<QuizAttemptAnswer> \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/quizzes/3d477cf4-3d47-7d47-8477-3d477cf43d47/attempts/ca174553-ca17-7a17-8174-ca174553ca17/answers/53cf0780-53cf-73cf-8cf0-53cf078053cf/grade" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "pointsAwarded": 0,
    "feedbackMd": ""
  }'
```

## POST /api/v1/quizzes/{lessonId}/publish-to-gradebook

Publish each student’s best fully-graded attempt into the gradebook as a `quiz` exam per group — one click, upserts on repeat.

- Operation id: `QuizAttemptsController_publishToGradebook_v1`
- Permission: `curriculum.assignments.grade`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `published` | array<object> | yes |  |
| `skipped` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/quizzes/3d477cf4-3d47-7d47-8477-3d477cf43d47/publish-to-gradebook" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/quizzes/{lessonId}/questions

Add a question to a quiz. Returns the whole quiz (questions included).

- Operation id: `QuizzesController_addQuestion_v1`
- Permission: `curriculum.quizzes.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `kind` | enum: "single" \| "multiple" \| "true_false" \| "short_text" | yes |  |
| `promptMd` | string | yes | 1–5000 chars |
| `options` | array<object> | no |  |
| `correct` | string \| array<string> \| boolean \| null | no |  |
| `points` | number | yes | 0–1000 |

```json
{
  "kind": "single",
  "promptMd": "string",
  "options": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "textMd": "string"
    }
  ],
  "correct": "string",
  "points": 0
}
```

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `timeLimitMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `attemptsAllowed` | integer | yes | 0–9007199254740991 |
| `shuffle` | boolean | yes |  |
| `showAnswersAfter` | enum: "never" \| "after_submit" \| "after_due" | yes |  |
| `passScore` | number \| null | yes |  |
| `dueAt` | string (date-time) \| null | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `questions` | array<QuizQuestion> | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/quizzes/3d477cf4-3d47-7d47-8477-3d477cf43d47/questions" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "kind": "single",
    "promptMd": "string",
    "options": [
      {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "textMd": "string"
      }
    ],
    "correct": "string",
    "points": 0
  }'
```

## DELETE /api/v1/quizzes/{lessonId}/questions/{questionId}

Delete a question. Returns the whole quiz (questions included).

- Operation id: `QuizzesController_removeQuestion_v1`
- Permission: `curriculum.quizzes.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `questionId` | path | string | yes |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `timeLimitMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `attemptsAllowed` | integer | yes | 0–9007199254740991 |
| `shuffle` | boolean | yes |  |
| `showAnswersAfter` | enum: "never" \| "after_submit" \| "after_due" | yes |  |
| `passScore` | number \| null | yes |  |
| `dueAt` | string (date-time) \| null | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `questions` | array<QuizQuestion> | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X DELETE "https://your-institute.example/api/v1/quizzes/3d477cf4-3d47-7d47-8477-3d477cf43d47/questions/53cf0780-53cf-73cf-8cf0-53cf078053cf" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PATCH /api/v1/quizzes/{lessonId}/questions/{questionId}

Update a question. Returns the whole quiz (questions included).

- Operation id: `QuizzesController_updateQuestion_v1`
- Permission: `curriculum.quizzes.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |
| `questionId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `kind` | enum: "single" \| "multiple" \| "true_false" \| "short_text" | no |  |
| `promptMd` | string | no | 1–5000 chars |
| `options` | array<object> | no |  |
| `correct` | string \| array<string> \| boolean \| null | no |  |
| `points` | number | no | 0–1000 |

```json
{
  "kind": "single",
  "promptMd": "string",
  "options": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "textMd": "string"
    }
  ],
  "correct": "string",
  "points": 0
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `timeLimitMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `attemptsAllowed` | integer | yes | 0–9007199254740991 |
| `shuffle` | boolean | yes |  |
| `showAnswersAfter` | enum: "never" \| "after_submit" \| "after_due" | yes |  |
| `passScore` | number \| null | yes |  |
| `dueAt` | string (date-time) \| null | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `questions` | array<QuizQuestion> | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PATCH "https://your-institute.example/api/v1/quizzes/3d477cf4-3d47-7d47-8477-3d477cf43d47/questions/53cf0780-53cf-73cf-8cf0-53cf078053cf" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "kind": "single",
    "promptMd": "string",
    "options": [
      {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "textMd": "string"
      }
    ],
    "correct": "string",
    "points": 0
  }'
```

## PUT /api/v1/quizzes/{lessonId}/questions/order

Whole-list reorder of a quiz’s questions. Returns the whole quiz (questions included).

- Operation id: `QuizzesController_reorderQuestions_v1`
- Permission: `curriculum.quizzes.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `lessonId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `questionIds` | array<string (uuid)> | yes |  |

```json
{
  "questionIds": [
    "e1e98389-e1e9-71e9-8e98-e1e98389e1e9"
  ]
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `lessonId` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `timeLimitMinutes` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `attemptsAllowed` | integer | yes | 0–9007199254740991 |
| `shuffle` | boolean | yes |  |
| `showAnswersAfter` | enum: "never" \| "after_submit" \| "after_due" | yes |  |
| `passScore` | number \| null | yes |  |
| `dueAt` | string (date-time) \| null | yes |  |
| `publishedAt` | string (date-time) \| null | yes |  |
| `questions` | array<QuizQuestion> | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/quizzes/3d477cf4-3d47-7d47-8477-3d477cf43d47/questions/order" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "questionIds": [
      "e1e98389-e1e9-71e9-8e98-e1e98389e1e9"
    ]
  }'
```

## DELETE /api/v1/sections/{id}

Delete a section. Fails with 409 if it still has lessons.

- Operation id: `SectionsController_remove_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Responses

**204** — The section was deleted.

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X DELETE "https://your-institute.example/api/v1/sections/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PATCH /api/v1/sections/{id}

Update a section.

- Operation id: `SectionsController_update_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `title` | string | no | 1–200 chars |
| `description` | enum: "" \| string \| null | no |  |

```json
{
  "title": "Midterm Exam",
  "description": ""
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `courseId` | string (uuid) | yes |  |
| `title` | string | yes | ≥1 chars |
| `description` | string \| null | yes | ≥1 chars |
| `sortOrder` | integer | yes | -9007199254740991–9007199254740991 |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PATCH "https://your-institute.example/api/v1/sections/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "title": "Midterm Exam",
    "description": ""
  }'
```

## PUT /api/v1/sections/{sectionId}/lessons/order

Reorder every lesson of a section in one call (whole-list, unpaginated response envelope).

- Operation id: `LessonsController_reorder_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `sectionId` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `lessonIds` | array<string (uuid)> | yes |  |

```json
{
  "lessonIds": [
    "fe890885-fe89-7e89-8890-fe890885fe89"
  ]
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/sections/c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab/lessons/order" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "lessonIds": [
      "fe890885-fe89-7e89-8890-fe890885fe89"
    ]
  }'
```

## GET /api/v1/students/{studentId}/course-enrollments

List one student’s self-paced course enrollments (cursor-paginated).

- Operation id: `CourseEnrollmentsController_listForStudent_v1`
- Permission: `enrollments.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `studentId` | path | string | yes |  |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |
| `status` | query | enum: "pending" \| "active" \| "completed" \| "dropped" | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/students/a4a70332-a4a7-74a7-8a70-a4a70332a4a7/course-enrollments" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/students/{studentId}/course-progress

One student’s per-course curriculum progress (cursor-paginated, row-scoped).

- Operation id: `StudentProgressController_list_v1`
- Permission: `curriculum.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `studentId` | path | string | yes |  |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |
| `studentId` | query | string (uuid) | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/students/a4a70332-a4a7-74a7-8a70-a4a70332a4a7/course-progress" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/video-sources/resolve

Parse and (best-effort, YouTube only) enrich a pasted video URL, without saving it anywhere.

- Operation id: `VideoSourcesController_resolve_v1`
- Permission: `curriculum.write`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `url` | string | yes | 1–2048 chars |

```json
{
  "url": "https://example.com/webhooks/institflow"
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `provider` | enum: "youtube" | yes |  |
| `externalRef` | string | yes | ≥1 chars |
| `canonicalUrl` | string | yes |  |
| `embedUrl` | string | yes |  |
| `thumbnailUrl` | string \| null | yes |  |
| `title` | string \| null | yes | ≥1 chars |
| `durationSeconds` | integer \| null | yes | 1–9007199254740991 |
| `capabilities` | object | yes |  |
| `warnings` | array<enum: "metadata_unavailable" \| "privacy_must_be_unlisted"> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/video-sources/resolve" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "url": "https://example.com/webhooks/institflow"
  }'
```
