curriculum

Curriculum & Video

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

61 endpoints

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

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—

Request body

NameTypeConstraints
instructionsMd
enum: "" | string | null—
dueAt
string (date-time) | null—
maxScore
number0–1000
allowLate
boolean—
submissionKinds
array<enum: "text" | "file" | "link">—

Example

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

curl example

curl
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"
    ]
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
sectionIdrequired
string (uuid)—
titlerequired
string≥1 chars
summaryrequired
string | null≥1 chars
contentMdrequired
string | null≥1 chars
kindrequired
enum: "lesson" | "assignment" | "quiz"—
statusrequired
enum: "draft" | "published"—
publishedAtrequired
string (date-time) | null—
availableFromrequired
string (date-time) | null—
availableAfterDaysrequired
integer | null-9007199254740991–9007199254740991
estimatedMinutesrequired
integer | null-9007199254740991–9007199254740991
sortOrderrequired
integer-9007199254740991–9007199254740991
videorequired
unknown | null—
assignmentrequired
unknown | null—
quizrequired
unknown | null—
audiencerequired
LessonAudience—
reminderModerequired
enum: "inherit" | "on" | "off"—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "string",
  "contentMd": "string",
  "kind": "lesson",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "assignment": "value",
  "quiz": "value",
  "audience": {
    "restricted": true,
    "groups": [
      {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "name": "Algebra I"
      }
    ],
    "hidden": true
  },
  "reminderMode": "inherit",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopecurriculum.assignments.grade

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—

curl example

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

Responses

200
NameTypeConstraints
publishedrequired
array<object>—
skippedrequired
array<object>—
json
{
  "published": [
    {
      "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
      "groupId": "6dc7fce6-6dc7-7dc7-8c7f-6dc7fce66dc7",
      "examId": "6c140414-6c14-7c14-8140-6c1404146c14",
      "score": 1
    }
  ],
  "skipped": [
    {
      "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
      "reason": "no_group"
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.assignments.grade

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
cursor
querystring—
limit
queryinteger1–100 · default: 25
status
queryenum: "submitted" | "late" | "returned" | "graded"—

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "assignmentId": "c41b0f8d-c41b-741b-81b0-c41b0f8dc41b",
      "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
      "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
      "submittedAt": "2026-03-02T09:00:00.000Z",
      "textMd": "string",
      "link": "string",
      "files": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "originalName": "string",
          "mimeType": "string",
          "sizeBytes": 0,
          "downloadUrl": "string",
          "downloadUrlExpiresAt": "2026-03-02T09:00:00.000Z"
        }
      ],
      "status": "submitted",
      "score": 1,
      "maxScore": 1,
      "feedbackMd": "string",
      "gradedByUserId": "71e16c27-71e1-71e1-8e16-71e16c2771e1",
      "gradedByName": "string",
      "gradedAt": "2026-03-02T09:00:00.000Z",
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.assignments.grade

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
submissionIdrequired
pathstring—

Request body

NameTypeConstraints
scorerequired
number≥0
feedbackMd
enum: "" | string | null—

Example

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

curl example

curl
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": ""
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
assignmentIdrequired
string (uuid)—
lessonIdrequired
string (uuid)—
studentIdrequired
string (uuid)—
submittedAtrequired
string (date-time)—
textMdrequired
string | null≥1 chars
linkrequired
string | null—
filesrequired
array<SubmissionFile>—
statusrequired
enum: "submitted" | "late" | "returned" | "graded"—
scorerequired
number | null—
maxScorerequired
number—
feedbackMdrequired
string | null≥1 chars
gradedByUserIdrequired
string (uuid) | null—
gradedByNamerequired
string | null≥1 chars
gradedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "assignmentId": "c41b0f8d-c41b-741b-81b0-c41b0f8dc41b",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "submittedAt": "2026-03-02T09:00:00.000Z",
  "textMd": "string",
  "link": "string",
  "files": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "originalName": "string",
      "mimeType": "string",
      "sizeBytes": 0,
      "downloadUrl": "string",
      "downloadUrlExpiresAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "status": "submitted",
  "score": 1,
  "maxScore": 1,
  "feedbackMd": "string",
  "gradedByUserId": "71e16c27-71e1-71e1-8e16-71e16c2771e1",
  "gradedByName": "string",
  "gradedAt": "2026-03-02T09:00:00.000Z",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.assignments.grade

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
submissionIdrequired
pathstring—

Request body

NameTypeConstraints
feedbackMd
enum: "" | string | null—

Example

json
{
  "feedbackMd": ""
}

curl example

curl
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": ""
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
assignmentIdrequired
string (uuid)—
lessonIdrequired
string (uuid)—
studentIdrequired
string (uuid)—
submittedAtrequired
string (date-time)—
textMdrequired
string | null≥1 chars
linkrequired
string | null—
filesrequired
array<SubmissionFile>—
statusrequired
enum: "submitted" | "late" | "returned" | "graded"—
scorerequired
number | null—
maxScorerequired
number—
feedbackMdrequired
string | null≥1 chars
gradedByUserIdrequired
string (uuid) | null—
gradedByNamerequired
string | null≥1 chars
gradedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "assignmentId": "c41b0f8d-c41b-741b-81b0-c41b0f8dc41b",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "submittedAt": "2026-03-02T09:00:00.000Z",
  "textMd": "string",
  "link": "string",
  "files": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "originalName": "string",
      "mimeType": "string",
      "sizeBytes": 0,
      "downloadUrl": "string",
      "downloadUrlExpiresAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "status": "submitted",
  "score": 1,
  "maxScore": 1,
  "feedbackMd": "string",
  "gradedByUserId": "71e16c27-71e1-71e1-8e16-71e16c2771e1",
  "gradedByName": "string",
  "gradedAt": "2026-03-02T09:00:00.000Z",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopeenrollments.write

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—
reason
querystring1–500 chars

curl example

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

Responses

204The course enrollment was dropped.

The API's OpenAPI spec doesn't declare a typed schema for this response yet — it's still JSON, just not documented here.

400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—

Request body

NameTypeConstraints
sectionId
string (uuid)—
titlerequired
string1–200 chars
summary
enum: "" | string | null—
availableFrom
string (date-time) | null—
availableAfterDays
integer | null0–365
estimatedMinutes
integer | null1–600
instructionsMd
enum: "" | string | null—
dueAt
string (date-time) | null—
maxScorerequired
number0–1000
allowLate
booleandefault: false
submissionKindsrequired
array<enum: "text" | "file" | "link">—
audienceGroupIds
array<string (uuid)> | null—

Example

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"
  ]
}

curl example

curl
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"
    ]
  }'

Responses

201
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
sectionIdrequired
string (uuid)—
titlerequired
string≥1 chars
summaryrequired
string | null≥1 chars
contentMdrequired
string | null≥1 chars
kindrequired
enum: "lesson" | "assignment" | "quiz"—
statusrequired
enum: "draft" | "published"—
publishedAtrequired
string (date-time) | null—
availableFromrequired
string (date-time) | null—
availableAfterDaysrequired
integer | null-9007199254740991–9007199254740991
estimatedMinutesrequired
integer | null-9007199254740991–9007199254740991
sortOrderrequired
integer-9007199254740991–9007199254740991
videorequired
unknown | null—
assignmentrequired
unknown | null—
quizrequired
unknown | null—
audiencerequired
LessonAudience—
reminderModerequired
enum: "inherit" | "on" | "off"—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "string",
  "contentMd": "string",
  "kind": "lesson",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "assignment": "value",
  "quiz": "value",
  "audience": {
    "restricted": true,
    "groups": [
      {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "name": "Algebra I"
      }
    ],
    "hidden": true
  },
  "reminderMode": "inherit",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—
cursor
querystring—
limit
queryinteger1–100 · default: 25

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "name": "Algebra I"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.read

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—

curl example

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

Responses

200
NameTypeConstraints
courseIdrequired
string (uuid)—
sectionCountrequired
integer0–9007199254740991
lessonCountrequired
integer0–9007199254740991
publishedLessonCountrequired
integer0–9007199254740991
videoLessonCountrequired
integer0–9007199254740991
json
{
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "sectionCount": 0,
  "lessonCount": 0,
  "publishedLessonCount": 0,
  "videoLessonCount": 0
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopeenrollments.read

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—
cursor
querystring—
limit
queryinteger1–100 · default: 25
status
queryenum: "pending" | "active" | "completed" | "dropped"—

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
      "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
      "status": "pending",
      "grantedAt": "2026-03-02T09:00:00.000Z",
      "endedAt": "2026-03-02T09:00:00.000Z",
      "priceMinor": 12000,
      "currency": "ILS",
      "grantedByUserId": "fc4e27b3-fc4e-7c4e-84e2-fc4e27b3fc4e",
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

Requires authenticationScopeenrollments.write

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—

Request body

NameTypeConstraints
studentIdrequired
string (uuid)—
priceMinor
integer0–9007199254740991
currency
enum: "ILS" | "USD" | "EUR"—

Example

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

curl example

curl
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"
  }'

Responses

201
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
studentIdrequired
string (uuid)—
statusrequired
enum: "pending" | "active" | "completed" | "dropped"—
grantedAtrequired
string (date-time)—
endedAtrequired
string (date-time) | null—
priceMinorrequired
integer | null-9007199254740991–9007199254740991
currencyrequired
enum: "ILS" | "USD" | "EUR" | null | null—
grantedByUserIdrequired
string (uuid) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "status": "pending",
  "grantedAt": "2026-03-02T09:00:00.000Z",
  "endedAt": "2026-03-02T09:00:00.000Z",
  "priceMinor": 12000,
  "currency": "ILS",
  "grantedByUserId": "fc4e27b3-fc4e-7c4e-84e2-fc4e27b3fc4e",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.read

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—
cursor
querystring—
limit
queryinteger1–100 · default: 25
sectionId
querystring (uuid)—
status
queryenum: "draft" | "published"—

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
      "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
      "title": "Midterm Exam",
      "summary": "string",
      "contentMd": "string",
      "kind": "lesson",
      "status": "draft",
      "publishedAt": "2026-03-02T09:00:00.000Z",
      "availableFrom": "2026-03-02T09:00:00.000Z",
      "availableAfterDays": -9007199254740991,
      "estimatedMinutes": -9007199254740991,
      "sortOrder": -9007199254740991,
      "video": "value",
      "assignment": "value",
      "quiz": "value",
      "audience": {
        "restricted": true,
        "groups": [
          {
            "id": "37386ae0-3738-7738-8386-37386ae03738",
            "name": "Algebra I"
          }
        ],
        "hidden": true
      },
      "reminderMode": "inherit",
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—

Request body

NameTypeConstraints
sectionId
string (uuid)—
titlerequired
string1–200 chars
summary
enum: "" | string | null—
contentMd
enum: "" | string | null—
availableFrom
string (date-time) | null—
availableAfterDays
integer | null0–365
estimatedMinutes
integer | null1–600
audienceGroupIds
array<string (uuid)> | null—

Example

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"
  ]
}

curl example

curl
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"
    ]
  }'

Responses

201
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
sectionIdrequired
string (uuid)—
titlerequired
string≥1 chars
summaryrequired
string | null≥1 chars
contentMdrequired
string | null≥1 chars
kindrequired
enum: "lesson" | "assignment" | "quiz"—
statusrequired
enum: "draft" | "published"—
publishedAtrequired
string (date-time) | null—
availableFromrequired
string (date-time) | null—
availableAfterDaysrequired
integer | null-9007199254740991–9007199254740991
estimatedMinutesrequired
integer | null-9007199254740991–9007199254740991
sortOrderrequired
integer-9007199254740991–9007199254740991
videorequired
unknown | null—
assignmentrequired
unknown | null—
quizrequired
unknown | null—
audiencerequired
LessonAudience—
reminderModerequired
enum: "inherit" | "on" | "off"—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "string",
  "contentMd": "string",
  "kind": "lesson",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "assignment": "value",
  "quiz": "value",
  "audience": {
    "restricted": true,
    "groups": [
      {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "name": "Algebra I"
      }
    ],
    "hidden": true
  },
  "reminderMode": "inherit",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.read

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—
cursor
querystring—
limit
queryinteger1–100 · default: 25
search
querystring1–200 chars

curl example

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

Responses

200
NameTypeConstraints
courseIdrequired
string (uuid)—
unitsrequired
array<ProgressMatrixUnit>—
studentsrequired
array<ProgressMatrixStudent>—
cellsrequired
object—
nextCursorrequired
string | null—
json
{
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "units": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "title": "Midterm Exam",
      "kind": "lesson",
      "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
      "sectionTitle": "string",
      "sortOrder": -9007199254740991,
      "completedCount": 0
    }
  ],
  "students": [
    {
      "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
      "fullName": "Ada Lovelace",
      "groupIds": [
        "b0a20d96-b0a2-70a2-8a20-b0a20d96b0a2"
      ],
      "source": "course_enrollment",
      "completedCount": 0,
      "percent": 0
    }
  ],
  "cells": {},
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.read

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—
studentIdrequired
pathstring—

curl example

curl
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"

Responses

200
NameTypeConstraints
courseIdrequired
string (uuid)—
studentIdrequired
string (uuid)—
fullNamerequired
string≥1 chars
completedCountrequired
integer0–9007199254740991
percentrequired
integer0–100
unitsrequired
array<StudentProgressUnit>—
json
{
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "fullName": "Ada Lovelace",
  "completedCount": 0,
  "percent": 0,
  "units": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "title": "Midterm Exam",
      "kind": "lesson",
      "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
      "sectionTitle": "string",
      "sortOrder": -9007199254740991,
      "status": "not_started",
      "completedAt": "2026-03-02T09:00:00.000Z",
      "lastSeenAt": "2026-03-02T09:00:00.000Z",
      "assignment": "value",
      "quiz": "value"
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.quizzes.manage

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—

Request body

NameTypeConstraints
sectionId
string (uuid)—
titlerequired
string1–200 chars
summary
enum: "" | string | null—
availableFrom
string (date-time) | null—
availableAfterDays
integer | null0–365
estimatedMinutes
integer | null1–600
timeLimitMinutes
integer | null1–600
attemptsAllowed
integer1–20 · default: 1
shuffle
booleandefault: false
showAnswersAfter
enum: "never" | "after_submit" | "after_due"default: "after_submit"
passScore
number | null≤100000
dueAt
string (date-time) | null—
audienceGroupIds
array<string (uuid)> | null—

Example

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"
  ]
}

curl example

curl
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"
    ]
  }'

Responses

201
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
sectionIdrequired
string (uuid)—
titlerequired
string≥1 chars
summaryrequired
string | null≥1 chars
contentMdrequired
string | null≥1 chars
kindrequired
enum: "lesson" | "assignment" | "quiz"—
statusrequired
enum: "draft" | "published"—
publishedAtrequired
string (date-time) | null—
availableFromrequired
string (date-time) | null—
availableAfterDaysrequired
integer | null-9007199254740991–9007199254740991
estimatedMinutesrequired
integer | null-9007199254740991–9007199254740991
sortOrderrequired
integer-9007199254740991–9007199254740991
videorequired
unknown | null—
assignmentrequired
unknown | null—
quizrequired
unknown | null—
audiencerequired
LessonAudience—
reminderModerequired
enum: "inherit" | "on" | "off"—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "string",
  "contentMd": "string",
  "kind": "lesson",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "assignment": "value",
  "quiz": "value",
  "audience": {
    "restricted": true,
    "groups": [
      {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "name": "Algebra I"
      }
    ],
    "hidden": true
  },
  "reminderMode": "inherit",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.read

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—
cursor
querystring—
limit
queryinteger1–100 · default: 25

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
      "title": "Midterm Exam",
      "description": "A short, human-readable description.",
      "sortOrder": -9007199254740991,
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

Create a section.

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—

Request body

NameTypeConstraints
titlerequired
string1–200 chars
description
string | string—

Example

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

curl example

curl
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."
  }'

Responses

201
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
titlerequired
string≥1 chars
descriptionrequired
string | null≥1 chars
sortOrderrequired
integer-9007199254740991–9007199254740991
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "title": "Midterm Exam",
  "description": "A short, human-readable description.",
  "sortOrder": -9007199254740991,
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—

Request body

NameTypeConstraints
sectionIdsrequired
array<string (uuid)>—

Example

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

curl example

curl
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"
    ]
  }'

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
      "title": "Midterm Exam",
      "description": "A short, human-readable description.",
      "sortOrder": -9007199254740991,
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

GET /api/v1/curriculum/usage

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

Requires authenticationScopecurriculum.read

curl example

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

Responses

200
NameTypeConstraints
videoCoursesrequired
object—
lessonsPerCourserequired
object—
json
{
  "videoCourses": {
    "used": 0,
    "limit": -1
  },
  "lessonsPerCourse": {
    "limit": -1
  }
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

204The file was deleted.

The API's OpenAPI spec doesn't declare a typed schema for this response yet — it's still JSON, just not documented here.

400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
originalName
string1–255 chars
description
enum: "" | string | null—
visibility
enum: "learners" | "staff"—

Example

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

curl example

curl
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"
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
lessonIdrequired
string (uuid)—
originalNamerequired
string≥1 chars
mimeTyperequired
string≥1 chars
sizeBytesrequired
integer0–9007199254740991
descriptionrequired
string | null≥1 chars
visibilityrequired
enum: "learners" | "staff"—
sortOrderrequired
integer-9007199254740991–9007199254740991
createdAtrequired
string (date-time)—
downloadUrlrequired
string—
downloadUrlExpiresAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "originalName": "string",
  "mimeType": "string",
  "sizeBytes": 0,
  "description": "A short, human-readable description.",
  "visibility": "learners",
  "sortOrder": -9007199254740991,
  "createdAt": "2026-03-02T09:00:00.000Z",
  "downloadUrl": "string",
  "downloadUrlExpiresAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

DELETE /api/v1/lessons/{id}

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

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

204The lesson was deleted.

The API's OpenAPI spec doesn't declare a typed schema for this response yet — it's still JSON, just not documented here.

400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

GET /api/v1/lessons/{id}

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

Requires authenticationScopecurriculum.read

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
sectionIdrequired
string (uuid)—
titlerequired
string≥1 chars
summaryrequired
string | null≥1 chars
contentMdrequired
string | null≥1 chars
kindrequired
enum: "lesson" | "assignment" | "quiz"—
statusrequired
enum: "draft" | "published"—
publishedAtrequired
string (date-time) | null—
availableFromrequired
string (date-time) | null—
availableAfterDaysrequired
integer | null-9007199254740991–9007199254740991
estimatedMinutesrequired
integer | null-9007199254740991–9007199254740991
sortOrderrequired
integer-9007199254740991–9007199254740991
videorequired
unknown | null—
assignmentrequired
unknown | null—
quizrequired
unknown | null—
audiencerequired
LessonAudience—
reminderModerequired
enum: "inherit" | "on" | "off"—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "string",
  "contentMd": "string",
  "kind": "lesson",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "assignment": "value",
  "quiz": "value",
  "audience": {
    "restricted": true,
    "groups": [
      {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "name": "Algebra I"
      }
    ],
    "hidden": true
  },
  "reminderMode": "inherit",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

PATCH /api/v1/lessons/{id}

Update a lesson.

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
sectionId
string (uuid)—
title
string1–200 chars
summary
enum: "" | string | null—
contentMd
enum: "" | string | null—
availableFrom
string (date-time) | null—
availableAfterDays
integer | null0–365
estimatedMinutes
integer | null1–600
audienceGroupIds
array<string (uuid)> | null—
reminderMode
enum: "inherit" | "on" | "off"—

Example

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"
}

curl example

curl
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"
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
sectionIdrequired
string (uuid)—
titlerequired
string≥1 chars
summaryrequired
string | null≥1 chars
contentMdrequired
string | null≥1 chars
kindrequired
enum: "lesson" | "assignment" | "quiz"—
statusrequired
enum: "draft" | "published"—
publishedAtrequired
string (date-time) | null—
availableFromrequired
string (date-time) | null—
availableAfterDaysrequired
integer | null-9007199254740991–9007199254740991
estimatedMinutesrequired
integer | null-9007199254740991–9007199254740991
sortOrderrequired
integer-9007199254740991–9007199254740991
videorequired
unknown | null—
assignmentrequired
unknown | null—
quizrequired
unknown | null—
audiencerequired
LessonAudience—
reminderModerequired
enum: "inherit" | "on" | "off"—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "string",
  "contentMd": "string",
  "kind": "lesson",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "assignment": "value",
  "quiz": "value",
  "audience": {
    "restricted": true,
    "groups": [
      {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "name": "Algebra I"
      }
    ],
    "hidden": true
  },
  "reminderMode": "inherit",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.publish

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
sectionIdrequired
string (uuid)—
titlerequired
string≥1 chars
summaryrequired
string | null≥1 chars
contentMdrequired
string | null≥1 chars
kindrequired
enum: "lesson" | "assignment" | "quiz"—
statusrequired
enum: "draft" | "published"—
publishedAtrequired
string (date-time) | null—
availableFromrequired
string (date-time) | null—
availableAfterDaysrequired
integer | null-9007199254740991–9007199254740991
estimatedMinutesrequired
integer | null-9007199254740991–9007199254740991
sortOrderrequired
integer-9007199254740991–9007199254740991
videorequired
unknown | null—
assignmentrequired
unknown | null—
quizrequired
unknown | null—
audiencerequired
LessonAudience—
reminderModerequired
enum: "inherit" | "on" | "off"—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "string",
  "contentMd": "string",
  "kind": "lesson",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "assignment": "value",
  "quiz": "value",
  "audience": {
    "restricted": true,
    "groups": [
      {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "name": "Algebra I"
      }
    ],
    "hidden": true
  },
  "reminderMode": "inherit",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

Unpublish a lesson back to draft.

Requires authenticationScopecurriculum.publish

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
sectionIdrequired
string (uuid)—
titlerequired
string≥1 chars
summaryrequired
string | null≥1 chars
contentMdrequired
string | null≥1 chars
kindrequired
enum: "lesson" | "assignment" | "quiz"—
statusrequired
enum: "draft" | "published"—
publishedAtrequired
string (date-time) | null—
availableFromrequired
string (date-time) | null—
availableAfterDaysrequired
integer | null-9007199254740991–9007199254740991
estimatedMinutesrequired
integer | null-9007199254740991–9007199254740991
sortOrderrequired
integer-9007199254740991–9007199254740991
videorequired
unknown | null—
assignmentrequired
unknown | null—
quizrequired
unknown | null—
audiencerequired
LessonAudience—
reminderModerequired
enum: "inherit" | "on" | "off"—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "string",
  "contentMd": "string",
  "kind": "lesson",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "assignment": "value",
  "quiz": "value",
  "audience": {
    "restricted": true,
    "groups": [
      {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "name": "Algebra I"
      }
    ],
    "hidden": true
  },
  "reminderMode": "inherit",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

Remove a lesson’s video (idempotent).

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

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

The API's OpenAPI spec doesn't declare a typed schema for this response yet — it's still JSON, just not documented here.

400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
urlrequired
string1–2048 chars
title
string1–200 chars
durationSeconds
integer1–86400

Example

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

curl example

curl
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
  }'

Responses

200
NameTypeConstraints
providerrequired
enum: "youtube"—
externalRefrequired
string≥1 chars
canonicalUrlrequired
string—
embedUrlrequired
string—
titlerequired
string | null≥1 chars
thumbnailUrlrequired
string | null—
durationSecondsrequired
integer | null1–9007199254740991
verifiedAtrequired
string (date-time) | null—
capabilitiesrequired
object—
json
{
  "provider": "youtube",
  "externalRef": "string",
  "canonicalUrl": "string",
  "embedUrl": "string",
  "title": "Midterm Exam",
  "thumbnailUrl": "string",
  "durationSeconds": 1,
  "verifiedAt": "2026-03-02T09:00:00.000Z",
  "capabilities": {
    "progressTracking": true,
    "derivedThumbnail": true,
    "serverMetadata": true,
    "thirdPartyAds": true
  }
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200
NameTypeConstraints
providerrequired
enum: "youtube"—
externalRefrequired
string≥1 chars
canonicalUrlrequired
string—
embedUrlrequired
string—
titlerequired
string | null≥1 chars
thumbnailUrlrequired
string | null—
durationSecondsrequired
integer | null1–9007199254740991
verifiedAtrequired
string (date-time) | null—
capabilitiesrequired
object—
json
{
  "provider": "youtube",
  "externalRef": "string",
  "canonicalUrl": "string",
  "embedUrl": "string",
  "title": "Midterm Exam",
  "thumbnailUrl": "string",
  "durationSeconds": 1,
  "verifiedAt": "2026-03-02T09:00:00.000Z",
  "capabilities": {
    "progressTracking": true,
    "derivedThumbnail": true,
    "serverMetadata": true,
    "thirdPartyAds": true
  }
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

Requires authenticationScopecurriculum.read

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<LessonFile>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
      "originalName": "string",
      "mimeType": "string",
      "sizeBytes": 0,
      "description": "A short, human-readable description.",
      "visibility": "learners",
      "sortOrder": -9007199254740991,
      "createdAt": "2026-03-02T09:00:00.000Z",
      "downloadUrl": "string",
      "downloadUrlExpiresAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—

Request body

NameTypeConstraints
originalNamerequired
string1–255 chars
mimeTyperequired
string1–127 chars
sizeBytesrequired
integer1–20971520
fileId
string (uuid)—
storageKeyrequired
string1–1024 chars
sha256required
string—
description
string | string—
visibility
enum: "learners" | "staff"—

Example

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"
}

curl example

curl
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"
  }'

Responses

201
NameTypeConstraints
idrequired
string (uuid)—
lessonIdrequired
string (uuid)—
originalNamerequired
string≥1 chars
mimeTyperequired
string≥1 chars
sizeBytesrequired
integer0–9007199254740991
descriptionrequired
string | null≥1 chars
visibilityrequired
enum: "learners" | "staff"—
sortOrderrequired
integer-9007199254740991–9007199254740991
createdAtrequired
string (date-time)—
downloadUrlrequired
string—
downloadUrlExpiresAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "originalName": "string",
  "mimeType": "string",
  "sizeBytes": 0,
  "description": "A short, human-readable description.",
  "visibility": "learners",
  "sortOrder": -9007199254740991,
  "createdAt": "2026-03-02T09:00:00.000Z",
  "downloadUrl": "string",
  "downloadUrlExpiresAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—

Request body

NameTypeConstraints
fileIdsrequired
array<string (uuid)>—

Example

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

curl example

curl
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"
    ]
  }'

Responses

200
NameTypeConstraints
datarequired
array<LessonFile>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
      "originalName": "string",
      "mimeType": "string",
      "sizeBytes": 0,
      "description": "A short, human-readable description.",
      "visibility": "learners",
      "sortOrder": -9007199254740991,
      "createdAt": "2026-03-02T09:00:00.000Z",
      "downloadUrl": "string",
      "downloadUrlExpiresAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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`).

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—

Request body

NameTypeConstraints
originalNamerequired
string1–255 chars
mimeTyperequired
string1–127 chars
sizeBytesrequired
integer1–20971520

Example

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

curl example

curl
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
  }'

Responses

201
NameTypeConstraints
storageKeyrequired
string—
uploadUrlrequired
string—
expiresAtrequired
string (date-time)—
json
{
  "storageKey": "string",
  "uploadUrl": "string",
  "expiresAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
confirmDeleteSubmissions
queryenum: "true" | "false"—

curl example

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

Responses

204The task was removed.

The API's OpenAPI spec doesn't declare a typed schema for this response yet — it's still JSON, just not documented here.

400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—

Request body

NameTypeConstraints
instructionsMdrequired
string1–20000 chars
dueAt
string (date-time) | null—
maxScorerequired
number0–1000
allowLate
booleandefault: false
submissionKindsrequired
array<enum: "text" | "file" | "link">—

Example

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

curl example

curl
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"
    ]
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
sectionIdrequired
string (uuid)—
titlerequired
string≥1 chars
summaryrequired
string | null≥1 chars
contentMdrequired
string | null≥1 chars
kindrequired
enum: "lesson" | "assignment" | "quiz"—
statusrequired
enum: "draft" | "published"—
publishedAtrequired
string (date-time) | null—
availableFromrequired
string (date-time) | null—
availableAfterDaysrequired
integer | null-9007199254740991–9007199254740991
estimatedMinutesrequired
integer | null-9007199254740991–9007199254740991
sortOrderrequired
integer-9007199254740991–9007199254740991
videorequired
unknown | null—
assignmentrequired
unknown | null—
quizrequired
unknown | null—
audiencerequired
LessonAudience—
reminderModerequired
enum: "inherit" | "on" | "off"—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "string",
  "contentMd": "string",
  "kind": "lesson",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "assignment": "value",
  "quiz": "value",
  "audience": {
    "restricted": true,
    "groups": [
      {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "name": "Algebra I"
      }
    ],
    "hidden": true
  },
  "reminderMode": "inherit",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

GET /api/v1/me/courses

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

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25
studentId
querystring (uuid)—

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<MyCourseResponse>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
      "name": "Algebra I",
      "deliveryMode": "group",
      "lessonCount": 0,
      "completedCount": 0,
      "progressPercent": 0,
      "lastLesson": "value",
      "coverThumbnailUrl": "string"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
courseIdrequired
pathstring—
cursor
querystring—
limit
queryinteger1–100 · default: 25
studentId
querystring (uuid)—

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<MyLessonListItem>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
      "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
      "sectionTitle": "string",
      "title": "Midterm Exam",
      "summary": "string",
      "kind": "lesson",
      "estimatedMinutes": -9007199254740991,
      "hasVideo": true,
      "locked": true,
      "availableAt": "2026-03-02T09:00:00.000Z",
      "progress": "value"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
studentId
querystring (uuid)—

curl example

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

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
sectionIdrequired
string (uuid)—
sectionTitlerequired
string≥1 chars
titlerequired
string≥1 chars
summaryrequired
string | null≥1 chars
kindrequired
enum: "lesson" | "assignment" | "quiz"—
estimatedMinutesrequired
integer | null-9007199254740991–9007199254740991
hasVideorequired
boolean—
lockedrequired
boolean—
availableAtrequired
string (date-time) | null—
progressrequired
unknown | null—
contentMdrequired
string | null≥1 chars
attachmentsrequired
array<MyLessonAttachment>—
videorequired
unknown | null—
assignmentrequired
unknown | null—
quizrequired
unknown | null—
previousLessonIdrequired
string (uuid) | null—
nextLessonIdrequired
string (uuid) | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "sectionTitle": "string",
  "title": "Midterm Exam",
  "summary": "string",
  "kind": "lesson",
  "estimatedMinutes": -9007199254740991,
  "hasVideo": true,
  "locked": true,
  "availableAt": "2026-03-02T09:00:00.000Z",
  "progress": "value",
  "contentMd": "string",
  "attachments": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "originalName": "string",
      "mimeType": "string",
      "sizeBytes": 0,
      "description": "A short, human-readable description.",
      "downloadUrl": "string",
      "downloadUrlExpiresAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "video": "value",
  "assignment": "value",
  "quiz": "value",
  "previousLessonId": "8830a901-8830-7830-830a-8830a9018830",
  "nextLessonId": "289c6ac1-289c-789c-89c6-289c6ac1289c"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
studentId
querystring (uuid)—

Request body

NameTypeConstraints
positionSecondsrequired
integer0–86400
watchedSeconds
integer0–86400
completed
boolean—

Example

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

curl example

curl
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
  }'

Responses

200
NameTypeConstraints
lessonIdrequired
string (uuid)—
studentIdrequired
string (uuid)—
statusrequired
enum: "in_progress" | "completed"—
lastPositionSecondsrequired
integer0–9007199254740991
watchedSecondsrequired
integer0–9007199254740991
firstStartedAtrequired
string (date-time)—
lastSeenAtrequired
string (date-time)—
completedAtrequired
string (date-time) | null—
json
{
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "status": "in_progress",
  "lastPositionSeconds": 0,
  "watchedSeconds": 0,
  "firstStartedAt": "2026-03-02T09:00:00.000Z",
  "lastSeenAt": "2026-03-02T09:00:00.000Z",
  "completedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
studentId
querystring (uuid)—

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "quizId": "a5ed7659-a5ed-75ed-8ed7-a5ed7659a5ed",
      "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
      "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
      "attemptNumber": -9007199254740991,
      "status": "in_progress",
      "startedAt": "2026-03-02T09:00:00.000Z",
      "submittedAt": "2026-03-02T09:00:00.000Z",
      "deadlineAt": "2026-03-02T09:00:00.000Z",
      "score": 1,
      "maxScore": 1,
      "passed": true,
      "questions": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "kind": "single",
          "promptMd": "string",
          "options": [
            {
              "id": "37386ae0-3738-7738-8386-37386ae03738",
              "textMd": "string"
            }
          ],
          "points": 0
        }
      ],
      "answers": [
        {
          "questionId": "53cf0780-53cf-73cf-8cf0-53cf078053cf",
          "answer": "string",
          "isCorrect": true,
          "pointsAwarded": 1,
          "needsGrading": true,
          "feedbackMd": "string",
          "correctAnswer": "string"
        }
      ]
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
studentId
querystring (uuid)—

curl example

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

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
quizIdrequired
string (uuid)—
lessonIdrequired
string (uuid)—
studentIdrequired
string (uuid)—
attemptNumberrequired
integer-9007199254740991–9007199254740991
statusrequired
enum: "in_progress" | "submitted" | "expired"—
startedAtrequired
string (date-time)—
submittedAtrequired
string (date-time) | null—
deadlineAtrequired
string (date-time) | null—
scorerequired
number | null—
maxScorerequired
number—
passedrequired
boolean | null—
questionsrequired
array<QuizAttemptQuestion>—
answersrequired
array<QuizAttemptAnswer> | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "quizId": "a5ed7659-a5ed-75ed-8ed7-a5ed7659a5ed",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "attemptNumber": -9007199254740991,
  "status": "in_progress",
  "startedAt": "2026-03-02T09:00:00.000Z",
  "submittedAt": "2026-03-02T09:00:00.000Z",
  "deadlineAt": "2026-03-02T09:00:00.000Z",
  "score": 1,
  "maxScore": 1,
  "passed": true,
  "questions": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "kind": "single",
      "promptMd": "string",
      "options": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "textMd": "string"
        }
      ],
      "points": 0
    }
  ],
  "answers": [
    {
      "questionId": "53cf0780-53cf-73cf-8cf0-53cf078053cf",
      "answer": "string",
      "isCorrect": true,
      "pointsAwarded": 1,
      "needsGrading": true,
      "feedbackMd": "string",
      "correctAnswer": "string"
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

Read one of my attempts.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
attemptIdrequired
pathstring—
studentId
querystring (uuid)—

curl example

curl
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"

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
quizIdrequired
string (uuid)—
lessonIdrequired
string (uuid)—
studentIdrequired
string (uuid)—
attemptNumberrequired
integer-9007199254740991–9007199254740991
statusrequired
enum: "in_progress" | "submitted" | "expired"—
startedAtrequired
string (date-time)—
submittedAtrequired
string (date-time) | null—
deadlineAtrequired
string (date-time) | null—
scorerequired
number | null—
maxScorerequired
number—
passedrequired
boolean | null—
questionsrequired
array<QuizAttemptQuestion>—
answersrequired
array<QuizAttemptAnswer> | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "quizId": "a5ed7659-a5ed-75ed-8ed7-a5ed7659a5ed",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "attemptNumber": -9007199254740991,
  "status": "in_progress",
  "startedAt": "2026-03-02T09:00:00.000Z",
  "submittedAt": "2026-03-02T09:00:00.000Z",
  "deadlineAt": "2026-03-02T09:00:00.000Z",
  "score": 1,
  "maxScore": 1,
  "passed": true,
  "questions": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "kind": "single",
      "promptMd": "string",
      "options": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "textMd": "string"
        }
      ],
      "points": 0
    }
  ],
  "answers": [
    {
      "questionId": "53cf0780-53cf-73cf-8cf0-53cf078053cf",
      "answer": "string",
      "isCorrect": true,
      "pointsAwarded": 1,
      "needsGrading": true,
      "feedbackMd": "string",
      "correctAnswer": "string"
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
attemptIdrequired
pathstring—
questionIdrequired
pathstring—
studentId
querystring (uuid)—

Request body

NameTypeConstraints
answerrequired
string | array<string> | boolean | null—

Example

json
{
  "answer": "string"
}

curl example

curl
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"
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
quizIdrequired
string (uuid)—
lessonIdrequired
string (uuid)—
studentIdrequired
string (uuid)—
attemptNumberrequired
integer-9007199254740991–9007199254740991
statusrequired
enum: "in_progress" | "submitted" | "expired"—
startedAtrequired
string (date-time)—
submittedAtrequired
string (date-time) | null—
deadlineAtrequired
string (date-time) | null—
scorerequired
number | null—
maxScorerequired
number—
passedrequired
boolean | null—
questionsrequired
array<QuizAttemptQuestion>—
answersrequired
array<QuizAttemptAnswer> | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "quizId": "a5ed7659-a5ed-75ed-8ed7-a5ed7659a5ed",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "attemptNumber": -9007199254740991,
  "status": "in_progress",
  "startedAt": "2026-03-02T09:00:00.000Z",
  "submittedAt": "2026-03-02T09:00:00.000Z",
  "deadlineAt": "2026-03-02T09:00:00.000Z",
  "score": 1,
  "maxScore": 1,
  "passed": true,
  "questions": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "kind": "single",
      "promptMd": "string",
      "options": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "textMd": "string"
        }
      ],
      "points": 0
    }
  ],
  "answers": [
    {
      "questionId": "53cf0780-53cf-73cf-8cf0-53cf078053cf",
      "answer": "string",
      "isCorrect": true,
      "pointsAwarded": 1,
      "needsGrading": true,
      "feedbackMd": "string",
      "correctAnswer": "string"
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
attemptIdrequired
pathstring—
studentId
querystring (uuid)—

curl example

curl
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"

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
quizIdrequired
string (uuid)—
lessonIdrequired
string (uuid)—
studentIdrequired
string (uuid)—
attemptNumberrequired
integer-9007199254740991–9007199254740991
statusrequired
enum: "in_progress" | "submitted" | "expired"—
startedAtrequired
string (date-time)—
submittedAtrequired
string (date-time) | null—
deadlineAtrequired
string (date-time) | null—
scorerequired
number | null—
maxScorerequired
number—
passedrequired
boolean | null—
questionsrequired
array<QuizAttemptQuestion>—
answersrequired
array<QuizAttemptAnswer> | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "quizId": "a5ed7659-a5ed-75ed-8ed7-a5ed7659a5ed",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "attemptNumber": -9007199254740991,
  "status": "in_progress",
  "startedAt": "2026-03-02T09:00:00.000Z",
  "submittedAt": "2026-03-02T09:00:00.000Z",
  "deadlineAt": "2026-03-02T09:00:00.000Z",
  "score": 1,
  "maxScore": 1,
  "passed": true,
  "questions": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "kind": "single",
      "promptMd": "string",
      "options": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "textMd": "string"
        }
      ],
      "points": 0
    }
  ],
  "answers": [
    {
      "questionId": "53cf0780-53cf-73cf-8cf0-53cf078053cf",
      "answer": "string",
      "isCorrect": true,
      "pointsAwarded": 1,
      "needsGrading": true,
      "feedbackMd": "string",
      "correctAnswer": "string"
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
studentId
querystring (uuid)—

Request body

NameTypeConstraints
textMd
enum: "" | string | null—
link
enum: "" | string | null—

Example

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

curl example

curl
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": ""
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
assignmentIdrequired
string (uuid)—
lessonIdrequired
string (uuid)—
studentIdrequired
string (uuid)—
submittedAtrequired
string (date-time)—
textMdrequired
string | null≥1 chars
linkrequired
string | null—
filesrequired
array<SubmissionFile>—
statusrequired
enum: "submitted" | "late" | "returned" | "graded"—
scorerequired
number | null—
maxScorerequired
number—
feedbackMdrequired
string | null≥1 chars
gradedByUserIdrequired
string (uuid) | null—
gradedByNamerequired
string | null≥1 chars
gradedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "assignmentId": "c41b0f8d-c41b-741b-81b0-c41b0f8dc41b",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "submittedAt": "2026-03-02T09:00:00.000Z",
  "textMd": "string",
  "link": "string",
  "files": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "originalName": "string",
      "mimeType": "string",
      "sizeBytes": 0,
      "downloadUrl": "string",
      "downloadUrlExpiresAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "status": "submitted",
  "score": 1,
  "maxScore": 1,
  "feedbackMd": "string",
  "gradedByUserId": "71e16c27-71e1-71e1-8e16-71e16c2771e1",
  "gradedByName": "string",
  "gradedAt": "2026-03-02T09:00:00.000Z",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
studentId
querystring (uuid)—

curl example

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

Responses

200
NameTypeConstraints
studentIdrequired
string (uuid)—
itemsrequired
array<NextUpItem>—
json
{
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "items": [
    {
      "kind": "assignment_due",
      "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
      "courseName": "string",
      "unitId": "6d6cc9ea-6d6c-7d6c-86cc-6d6cc9ea6d6c",
      "unitTitle": "string",
      "unitKind": "lesson",
      "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
      "sectionTitle": "string",
      "dueAt": "2026-03-02T09:00:00.000Z",
      "overdue": true
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

Requires authenticationScopecurriculum.quizzes.manage

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—

Request body

NameTypeConstraints
timeLimitMinutes
integer | null1–600
attemptsAllowed
integer1–20
shuffle
boolean—
showAnswersAfter
enum: "never" | "after_submit" | "after_due"—
passScore
number | null≤100000
dueAt
string (date-time) | null—

Example

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

curl example

curl
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"
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
sectionIdrequired
string (uuid)—
titlerequired
string≥1 chars
summaryrequired
string | null≥1 chars
contentMdrequired
string | null≥1 chars
kindrequired
enum: "lesson" | "assignment" | "quiz"—
statusrequired
enum: "draft" | "published"—
publishedAtrequired
string (date-time) | null—
availableFromrequired
string (date-time) | null—
availableAfterDaysrequired
integer | null-9007199254740991–9007199254740991
estimatedMinutesrequired
integer | null-9007199254740991–9007199254740991
sortOrderrequired
integer-9007199254740991–9007199254740991
videorequired
unknown | null—
assignmentrequired
unknown | null—
quizrequired
unknown | null—
audiencerequired
LessonAudience—
reminderModerequired
enum: "inherit" | "on" | "off"—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "string",
  "contentMd": "string",
  "kind": "lesson",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "assignment": "value",
  "quiz": "value",
  "audience": {
    "restricted": true,
    "groups": [
      {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "name": "Algebra I"
      }
    ],
    "hidden": true
  },
  "reminderMode": "inherit",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.assignments.grade

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
cursor
querystring—
limit
queryinteger1–100 · default: 25
studentId
querystring (uuid)—
status
queryenum: "in_progress" | "submitted"—

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "quizId": "a5ed7659-a5ed-75ed-8ed7-a5ed7659a5ed",
      "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
      "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
      "attemptNumber": -9007199254740991,
      "status": "in_progress",
      "startedAt": "2026-03-02T09:00:00.000Z",
      "submittedAt": "2026-03-02T09:00:00.000Z",
      "deadlineAt": "2026-03-02T09:00:00.000Z",
      "score": 1,
      "maxScore": 1,
      "passed": true,
      "questions": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "kind": "single",
          "promptMd": "string",
          "options": [
            {
              "id": "37386ae0-3738-7738-8386-37386ae03738",
              "textMd": "string"
            }
          ],
          "points": 0
        }
      ],
      "answers": [
        {
          "questionId": "53cf0780-53cf-73cf-8cf0-53cf078053cf",
          "answer": "string",
          "isCorrect": true,
          "pointsAwarded": 1,
          "needsGrading": true,
          "feedbackMd": "string",
          "correctAnswer": "string"
        }
      ]
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopecurriculum.assignments.grade

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
attemptIdrequired
pathstring—
questionIdrequired
pathstring—

Request body

NameTypeConstraints
pointsAwardedrequired
number≥0
feedbackMd
enum: "" | string | null—

Example

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

curl example

curl
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": ""
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
quizIdrequired
string (uuid)—
lessonIdrequired
string (uuid)—
studentIdrequired
string (uuid)—
attemptNumberrequired
integer-9007199254740991–9007199254740991
statusrequired
enum: "in_progress" | "submitted" | "expired"—
startedAtrequired
string (date-time)—
submittedAtrequired
string (date-time) | null—
deadlineAtrequired
string (date-time) | null—
scorerequired
number | null—
maxScorerequired
number—
passedrequired
boolean | null—
questionsrequired
array<QuizAttemptQuestion>—
answersrequired
array<QuizAttemptAnswer> | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "quizId": "a5ed7659-a5ed-75ed-8ed7-a5ed7659a5ed",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
  "attemptNumber": -9007199254740991,
  "status": "in_progress",
  "startedAt": "2026-03-02T09:00:00.000Z",
  "submittedAt": "2026-03-02T09:00:00.000Z",
  "deadlineAt": "2026-03-02T09:00:00.000Z",
  "score": 1,
  "maxScore": 1,
  "passed": true,
  "questions": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "kind": "single",
      "promptMd": "string",
      "options": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "textMd": "string"
        }
      ],
      "points": 0
    }
  ],
  "answers": [
    {
      "questionId": "53cf0780-53cf-73cf-8cf0-53cf078053cf",
      "answer": "string",
      "isCorrect": true,
      "pointsAwarded": 1,
      "needsGrading": true,
      "feedbackMd": "string",
      "correctAnswer": "string"
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopecurriculum.assignments.grade

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—

curl example

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

Responses

200
NameTypeConstraints
publishedrequired
array<object>—
skippedrequired
array<object>—
json
{
  "published": [
    {
      "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
      "groupId": "6dc7fce6-6dc7-7dc7-8c7f-6dc7fce66dc7",
      "examId": "6c140414-6c14-7c14-8140-6c1404146c14",
      "score": 1
    }
  ],
  "skipped": [
    {
      "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
      "reason": "no_group"
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.quizzes.manage

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—

Request body

NameTypeConstraints
kindrequired
enum: "single" | "multiple" | "true_false" | "short_text"—
promptMdrequired
string1–5000 chars
options
array<object>—
correct
string | array<string> | boolean | null—
pointsrequired
number0–1000

Example

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

curl example

curl
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
  }'

Responses

201
NameTypeConstraints
idrequired
string (uuid)—
lessonIdrequired
string (uuid)—
courseIdrequired
string (uuid)—
timeLimitMinutesrequired
integer | null-9007199254740991–9007199254740991
attemptsAllowedrequired
integer0–9007199254740991
shufflerequired
boolean—
showAnswersAfterrequired
enum: "never" | "after_submit" | "after_due"—
passScorerequired
number | null—
dueAtrequired
string (date-time) | null—
publishedAtrequired
string (date-time) | null—
questionsrequired
array<QuizQuestion>—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "timeLimitMinutes": -9007199254740991,
  "attemptsAllowed": 0,
  "shuffle": true,
  "showAnswersAfter": "never",
  "passScore": 1,
  "dueAt": "2026-03-02T09:00:00.000Z",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "questions": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "quizId": "a5ed7659-a5ed-75ed-8ed7-a5ed7659a5ed",
      "kind": "single",
      "promptMd": "string",
      "options": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "textMd": "string"
        }
      ],
      "correct": "string",
      "points": 0,
      "sortOrder": -9007199254740991,
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.quizzes.manage

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
questionIdrequired
pathstring—

curl example

curl
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"

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
lessonIdrequired
string (uuid)—
courseIdrequired
string (uuid)—
timeLimitMinutesrequired
integer | null-9007199254740991–9007199254740991
attemptsAllowedrequired
integer0–9007199254740991
shufflerequired
boolean—
showAnswersAfterrequired
enum: "never" | "after_submit" | "after_due"—
passScorerequired
number | null—
dueAtrequired
string (date-time) | null—
publishedAtrequired
string (date-time) | null—
questionsrequired
array<QuizQuestion>—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "timeLimitMinutes": -9007199254740991,
  "attemptsAllowed": 0,
  "shuffle": true,
  "showAnswersAfter": "never",
  "passScore": 1,
  "dueAt": "2026-03-02T09:00:00.000Z",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "questions": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "quizId": "a5ed7659-a5ed-75ed-8ed7-a5ed7659a5ed",
      "kind": "single",
      "promptMd": "string",
      "options": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "textMd": "string"
        }
      ],
      "correct": "string",
      "points": 0,
      "sortOrder": -9007199254740991,
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.quizzes.manage

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—
questionIdrequired
pathstring—

Request body

NameTypeConstraints
kind
enum: "single" | "multiple" | "true_false" | "short_text"—
promptMd
string1–5000 chars
options
array<object>—
correct
string | array<string> | boolean | null—
points
number0–1000

Example

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

curl example

curl
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
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
lessonIdrequired
string (uuid)—
courseIdrequired
string (uuid)—
timeLimitMinutesrequired
integer | null-9007199254740991–9007199254740991
attemptsAllowedrequired
integer0–9007199254740991
shufflerequired
boolean—
showAnswersAfterrequired
enum: "never" | "after_submit" | "after_due"—
passScorerequired
number | null—
dueAtrequired
string (date-time) | null—
publishedAtrequired
string (date-time) | null—
questionsrequired
array<QuizQuestion>—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "timeLimitMinutes": -9007199254740991,
  "attemptsAllowed": 0,
  "shuffle": true,
  "showAnswersAfter": "never",
  "passScore": 1,
  "dueAt": "2026-03-02T09:00:00.000Z",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "questions": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "quizId": "a5ed7659-a5ed-75ed-8ed7-a5ed7659a5ed",
      "kind": "single",
      "promptMd": "string",
      "options": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "textMd": "string"
        }
      ],
      "correct": "string",
      "points": 0,
      "sortOrder": -9007199254740991,
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.quizzes.manage

Path & query parameters

NameInTypeConstraints
lessonIdrequired
pathstring—

Request body

NameTypeConstraints
questionIdsrequired
array<string (uuid)>—

Example

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

curl example

curl
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"
    ]
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
lessonIdrequired
string (uuid)—
courseIdrequired
string (uuid)—
timeLimitMinutesrequired
integer | null-9007199254740991–9007199254740991
attemptsAllowedrequired
integer0–9007199254740991
shufflerequired
boolean—
showAnswersAfterrequired
enum: "never" | "after_submit" | "after_due"—
passScorerequired
number | null—
dueAtrequired
string (date-time) | null—
publishedAtrequired
string (date-time) | null—
questionsrequired
array<QuizQuestion>—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "lessonId": "3d477cf4-3d47-7d47-8477-3d477cf43d47",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "timeLimitMinutes": -9007199254740991,
  "attemptsAllowed": 0,
  "shuffle": true,
  "showAnswersAfter": "never",
  "passScore": 1,
  "dueAt": "2026-03-02T09:00:00.000Z",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "questions": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "quizId": "a5ed7659-a5ed-75ed-8ed7-a5ed7659a5ed",
      "kind": "single",
      "promptMd": "string",
      "options": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "textMd": "string"
        }
      ],
      "correct": "string",
      "points": 0,
      "sortOrder": -9007199254740991,
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

DELETE /api/v1/sections/{id}

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

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

204The section was deleted.

The API's OpenAPI spec doesn't declare a typed schema for this response yet — it's still JSON, just not documented here.

400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

PATCH /api/v1/sections/{id}

Update a section.

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
title
string1–200 chars
description
enum: "" | string | null—

Example

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

curl example

curl
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": ""
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
courseIdrequired
string (uuid)—
titlerequired
string≥1 chars
descriptionrequired
string | null≥1 chars
sortOrderrequired
integer-9007199254740991–9007199254740991
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
  "title": "Midterm Exam",
  "description": "A short, human-readable description.",
  "sortOrder": -9007199254740991,
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.write

Path & query parameters

NameInTypeConstraints
sectionIdrequired
pathstring—

Request body

NameTypeConstraints
lessonIdsrequired
array<string (uuid)>—

Example

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

curl example

curl
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"
    ]
  }'

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
      "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
      "title": "Midterm Exam",
      "summary": "string",
      "contentMd": "string",
      "kind": "lesson",
      "status": "draft",
      "publishedAt": "2026-03-02T09:00:00.000Z",
      "availableFrom": "2026-03-02T09:00:00.000Z",
      "availableAfterDays": -9007199254740991,
      "estimatedMinutes": -9007199254740991,
      "sortOrder": -9007199254740991,
      "video": "value",
      "assignment": "value",
      "quiz": "value",
      "audience": {
        "restricted": true,
        "groups": [
          {
            "id": "37386ae0-3738-7738-8386-37386ae03738",
            "name": "Algebra I"
          }
        ],
        "hidden": true
      },
      "reminderMode": "inherit",
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopeenrollments.read

Path & query parameters

NameInTypeConstraints
studentIdrequired
pathstring—
cursor
querystring—
limit
queryinteger1–100 · default: 25
status
queryenum: "pending" | "active" | "completed" | "dropped"—

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
      "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
      "status": "pending",
      "grantedAt": "2026-03-02T09:00:00.000Z",
      "endedAt": "2026-03-02T09:00:00.000Z",
      "priceMinor": 12000,
      "currency": "ILS",
      "grantedByUserId": "fc4e27b3-fc4e-7c4e-84e2-fc4e27b3fc4e",
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopecurriculum.read

Path & query parameters

NameInTypeConstraints
studentIdrequired
pathstring—
cursor
querystring—
limit
queryinteger1–100 · default: 25
studentId
querystring (uuid)—

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "studentId": "a4a70332-a4a7-74a7-8a70-a4a70332a4a7",
      "courseId": "d0e4743a-d0e4-70e4-8e47-d0e4743ad0e4",
      "courseName": "string",
      "lessonCount": 0,
      "completedCount": 0,
      "progressPercent": 0,
      "lastLessonId": "b9705f30-b970-7970-8705-b9705f30b970",
      "lastSeenAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

POST /api/v1/video-sources/resolve

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

Requires authenticationScopecurriculum.write

Request body

NameTypeConstraints
urlrequired
string1–2048 chars

Example

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

curl example

curl
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"
  }'

Responses

200
NameTypeConstraints
providerrequired
enum: "youtube"—
externalRefrequired
string≥1 chars
canonicalUrlrequired
string—
embedUrlrequired
string—
thumbnailUrlrequired
string | null—
titlerequired
string | null≥1 chars
durationSecondsrequired
integer | null1–9007199254740991
capabilitiesrequired
object—
warningsrequired
array<enum: "metadata_unavailable" | "privacy_must_be_unlisted">—
json
{
  "provider": "youtube",
  "externalRef": "string",
  "canonicalUrl": "string",
  "embedUrl": "string",
  "thumbnailUrl": "string",
  "title": "Midterm Exam",
  "durationSeconds": 1,
  "capabilities": {
    "progressTracking": true,
    "derivedThumbnail": true,
    "serverMetadata": true
  },
  "warnings": [
    "metadata_unavailable"
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.