curriculum

Curriculum & Video

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

28 endpoints

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://api.yourdomain.com/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.

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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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",
      "status": "draft",
      "publishedAt": "2026-03-02T09:00:00.000Z",
      "availableFrom": "2026-03-02T09:00:00.000Z",
      "availableAfterDays": -9007199254740991,
      "estimatedMinutes": -9007199254740991,
      "sortOrder": -9007199254740991,
      "video": "value",
      "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

Example

json
{
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "",
  "contentMd": "",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": 0,
  "estimatedMinutes": 1
}

curl example

curl
curl -X POST "https://api.yourdomain.com/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
  }'

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
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
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",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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/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://api.yourdomain.com/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://api.yourdomain.com/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
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
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",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "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

Example

json
{
  "sectionId": "c1ab4be1-c1ab-71ab-8ab4-c1ab4be1c1ab",
  "title": "Midterm Exam",
  "summary": "",
  "contentMd": "",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": 0,
  "estimatedMinutes": 1
}

curl example

curl
curl -X PATCH "https://api.yourdomain.com/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
  }'

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
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
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",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "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://api.yourdomain.com/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
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
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",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "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://api.yourdomain.com/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
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
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",
  "status": "draft",
  "publishedAt": "2026-03-02T09:00:00.000Z",
  "availableFrom": "2026-03-02T09:00:00.000Z",
  "availableAfterDays": -9007199254740991,
  "estimatedMinutes": -9007199254740991,
  "sortOrder": -9007199254740991,
  "video": "value",
  "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://api.yourdomain.com/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://api.yourdomain.com/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" | "terabox" | "upload" | "vimeo" | "bunny"
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://api.yourdomain.com/api/v1/lessons/37386ae0-3738-7738-8386-37386ae03738/video/verify" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200
NameTypeConstraints
providerrequired
enum: "youtube" | "terabox" | "upload" | "vimeo" | "bunny"
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/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://api.yourdomain.com/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://api.yourdomain.com/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",
      "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://api.yourdomain.com/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
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
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",
  "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,
      "downloadUrl": "string",
      "downloadUrlExpiresAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "video": "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://api.yourdomain.com/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.

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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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",
      "status": "draft",
      "publishedAt": "2026-03-02T09:00:00.000Z",
      "availableFrom": "2026-03-02T09:00:00.000Z",
      "availableAfterDays": -9007199254740991,
      "estimatedMinutes": -9007199254740991,
      "sortOrder": -9007199254740991,
      "video": "value",
      "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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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" | "terabox"
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" | "no_progress_tracking" | "third_party_ads" | "privacy_must_be_unlisted" | "link_may_expire">
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.