openapi: 3.0.0 info: title: Hexlet Basics API description: |- Hexlet Basics API. Contract-first (ADR-0001): this TypeSpec compiles to `dist/openapi.yaml` (`pnpm spec`), which drives BOTH the Go server (ogen validators) and the TS frontend client + zod (hey-api, `pnpm generate`). Never hand-edit `dist/`. Naming: the domain concept is `Course`. The legacy Rails app called it `Language`; we keep `Language` ONLY in URL paths (e.g. `/languages`) for backward compatibility (ADR-0002), but every type/operation/schema uses `Course`. The full model/enum set lives in `models.tsp`. Every operation is served under `/api` on the site's own host (ADR-0015): the ingress routes `/api/*` to Go and the rest to the SSR server, so the contract never collides with a page path and needs no CORS. The prefix is set once here, on the namespace every file reopens, so the operation routes below keep the legacy paths they mirror. version: 0.0.0 tags: - name: Courses - name: Course Categories - name: Blog - name: Reviews - name: Leads - name: Marketing - name: Sitemap - name: Feeds - name: Auth - name: User Area - name: 'Admin: Attachments' - name: 'Admin: Course Categories' - name: 'Admin: Banners' - name: 'Admin: Blog Posts' - name: 'Admin: Reviews' - name: 'Admin: Courses' - name: 'Admin: Course Landing Pages' - name: 'Admin: Course Lessons' - name: 'Admin: Lesson Progress' - name: 'Admin: Course Lesson Reviews' - name: 'Admin: Leads' - name: 'Admin: Messages' - name: 'Admin: Users' - name: 'Admin: Roles' - name: 'Admin: Staff Members' - name: Lesson Player paths: /api/account/profile: patch: operationId: updateProfile description: Update the current user's profile. parameters: [] responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/User' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '422': description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. content: application/json: schema: $ref: '#/components/schemas/ValidationError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - User Area requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProfileInput' security: - UserSession: [] XsrfToken: [] delete: operationId: deleteAccount description: Delete the current user's account. parameters: [] responses: '204': description: A successful sign-out with the expired auth cookies. headers: Set-Cookie: required: true schema: type: array items: type: string '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - User Area security: - UserSession: [] XsrfToken: [] /api/account/profile/edit: get: operationId: getProfile description: The editable profile of the current user. parameters: [] responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/User' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - User Area security: - UserSession: [] /api/admin/api/users: get: operationId: adminListUsers parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/UserCrudPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Users' security: - AdminSession: [] post: operationId: adminCreateUser description: |- Create a user. A duplicate email is a DB unique constraint, surfaced as 409 by the central ent-error handler. parameters: [] responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/UserCrud' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Users' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserInput' security: - AdminSession: [] XsrfToken: [] /api/admin/api/users/search: get: operationId: adminSearchUsers description: Typeahead search by name/email. parameters: - name: q in: query required: true schema: type: string explode: false responses: '200': description: The request has succeeded. content: application/json: schema: type: array items: $ref: '#/components/schemas/UserCrud' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Users' security: - AdminSession: [] /api/admin/api/users/{id}: get: operationId: adminGetUser description: |- Get a single user. A missing id surfaces as 404 via the central ent-error handler, not a typed union member. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/UserCrud' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Users' security: - AdminSession: [] put: operationId: adminUpdateUser description: |- Update a user. 404 (missing) and 409 (duplicate email) both flow through the central ent-error handler. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/UserCrud' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Users' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserInput' security: - AdminSession: [] XsrfToken: [] delete: operationId: adminDeleteUser parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Users' security: - AdminSession: [] XsrfToken: [] /api/admin/attachments: post: operationId: adminUploadAttachment description: Upload a file; returns the attachment to reference from a form. parameters: [] responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/Attachment' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '422': description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. content: application/json: schema: $ref: '#/components/schemas/ValidationError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Attachments' requestBody: required: true content: multipart/form-data: schema: $ref: '#/components/schemas/AttachmentUploadForm' security: - AdminSession: [] XsrfToken: [] /api/admin/banners: get: operationId: adminListBanners description: List banners (paginated). parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/BannerPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Banners' security: - AdminSession: [] post: operationId: adminCreateBanner description: |- Create a banner. A body violating the schema (e.g. an empty `body`) surfaces as 400 via the central handler; banners carry no uniqueness constraint, so there is no 409 path. parameters: [] responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/Banner' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Banners' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BannerInput' security: - AdminSession: [] XsrfToken: [] /api/admin/banners/{id}: get: operationId: adminGetBanner description: |- Get a single banner. A missing id surfaces as 404 via the central ent-error handler, not a typed union member. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/Banner' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Banners' security: - AdminSession: [] put: operationId: adminUpdateBanner description: Update a banner. A missing id surfaces as 404 via the central handler. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/Banner' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Banners' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BannerInput' security: - AdminSession: [] XsrfToken: [] delete: operationId: adminDeleteBanner description: Delete a banner. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Banners' security: - AdminSession: [] XsrfToken: [] /api/admin/blog_posts: get: operationId: adminListBlogPosts parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/BlogPostPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Blog Posts' security: - AdminSession: [] post: operationId: adminCreateBlogPost parameters: [] responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/BlogPost' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '422': description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. content: application/json: schema: $ref: '#/components/schemas/ValidationError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Blog Posts' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BlogPostInput' security: - AdminSession: [] XsrfToken: [] /api/admin/blog_posts/{id}: get: operationId: adminGetBlogPost parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/BlogPost' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Blog Posts' security: - AdminSession: [] put: operationId: adminUpdateBlogPost parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/BlogPost' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '422': description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. content: application/json: schema: $ref: '#/components/schemas/ValidationError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Blog Posts' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BlogPostInput' security: - AdminSession: [] XsrfToken: [] delete: operationId: adminDeleteBlogPost parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Blog Posts' security: - AdminSession: [] XsrfToken: [] /api/admin/blog_posts/{id}/related_courses: post: operationId: adminSetBlogPostRelatedCourses description: Set the related/promoted courses for a post by hand. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/BlogPost' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '422': description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. content: application/json: schema: $ref: '#/components/schemas/ValidationError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Blog Posts' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BlogPostRelatedCoursesInput' security: - AdminSession: [] XsrfToken: [] /api/admin/blog_posts/{id}/suggest_related_courses: post: operationId: adminSuggestBlogPostRelatedCourses description: |- Enqueue the AI pick of the post's related courses (legacy FindRelatedCoursesForBlogPostJob); the job replaces the set when done. A missing id surfaces as 404 via the central ent-error handler. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Blog Posts' security: - AdminSession: [] XsrfToken: [] /api/admin/course_categories: get: operationId: adminListCourseCategories description: List course categories (paginated). parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CourseCategoryPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Categories' security: - AdminSession: [] post: operationId: adminCreateCourseCategory description: |- Create a course category. A uniqueness violation (name/header/slug) is a DB constraint, surfaced as 409 by the central ent-error handler. parameters: [] responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/CourseCategory' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Categories' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CourseCategoryInput' security: - AdminSession: [] XsrfToken: [] /api/admin/course_categories/{categoryId}/qna_items: get: operationId: adminListCategoryQnaItems parameters: - name: categoryId in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: type: array items: $ref: '#/components/schemas/QnaItem' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Categories' security: - AdminSession: [] post: operationId: adminCreateCategoryQnaItem parameters: - name: categoryId in: path required: true schema: type: integer format: int32 responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/QnaItem' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Categories' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/QnaItemInput' security: - AdminSession: [] XsrfToken: [] /api/admin/course_categories/{categoryId}/qna_items/{id}: put: operationId: adminUpdateCategoryQnaItem description: |- Update a QnA item. A missing id (or one under a different parent) surfaces as 404 via the central ent-error handler. parameters: - name: categoryId in: path required: true schema: type: integer format: int32 - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/QnaItem' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Categories' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/QnaItemInput' security: - AdminSession: [] XsrfToken: [] delete: operationId: adminDeleteCategoryQnaItem parameters: - name: categoryId in: path required: true schema: type: integer format: int32 - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Categories' security: - AdminSession: [] XsrfToken: [] /api/admin/course_categories/{id}: get: operationId: adminGetCourseCategory description: |- Get a single course category. A missing id surfaces as 404 via the central ent-error handler, not a typed union member. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CourseCategory' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Categories' security: - AdminSession: [] put: operationId: adminUpdateCourseCategory description: |- Update a course category. 404 (missing) and 409 (uniqueness) both flow through the central ent-error handler. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CourseCategory' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Categories' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CourseCategoryInput' security: - AdminSession: [] XsrfToken: [] delete: operationId: adminDeleteCourseCategory description: Delete a course category. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Categories' security: - AdminSession: [] XsrfToken: [] /api/admin/course_landing_pages: get: operationId: adminListCourseLandingPages parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CourseLandingPagePage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Landing Pages' security: - AdminSession: [] post: operationId: adminCreateCourseLandingPage parameters: [] responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/CourseLandingPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Landing Pages' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CourseLandingPageInput' security: - AdminSession: [] XsrfToken: [] /api/admin/course_landing_pages/{id}: get: operationId: adminGetCourseLandingPage description: |- Get a single landing page. A missing id surfaces as 404 via the central ent-error handler, not a typed union member. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CourseLandingPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Landing Pages' security: - AdminSession: [] put: operationId: adminUpdateCourseLandingPage description: |- Update a landing page. A missing id surfaces as 404 via the central ent-error handler. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CourseLandingPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Landing Pages' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CourseLandingPageInput' security: - AdminSession: [] XsrfToken: [] delete: operationId: adminDeleteCourseLandingPage parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Landing Pages' security: - AdminSession: [] XsrfToken: [] /api/admin/course_landing_pages/{landingPageId}/qna_items: get: operationId: adminListLandingPageQnaItems parameters: - name: landingPageId in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: type: array items: $ref: '#/components/schemas/QnaItem' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Landing Pages' security: - AdminSession: [] post: operationId: adminCreateLandingPageQnaItem parameters: - name: landingPageId in: path required: true schema: type: integer format: int32 responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/QnaItem' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Landing Pages' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/QnaItemInput' security: - AdminSession: [] XsrfToken: [] /api/admin/course_landing_pages/{landingPageId}/qna_items/{id}: put: operationId: adminUpdateLandingPageQnaItem description: |- Update a QnA item. A missing id (or one under a different parent) surfaces as 404 via the central ent-error handler. parameters: - name: landingPageId in: path required: true schema: type: integer format: int32 - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/QnaItem' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Landing Pages' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/QnaItemInput' security: - AdminSession: [] XsrfToken: [] delete: operationId: adminDeleteLandingPageQnaItem parameters: - name: landingPageId in: path required: true schema: type: integer format: int32 - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Landing Pages' security: - AdminSession: [] XsrfToken: [] /api/admin/course_lesson_reviews: get: operationId: adminListCourseLessonReviews parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CourseLessonReviewPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Lesson Reviews' security: - AdminSession: [] /api/admin/course_lessons: get: operationId: adminListCourseLessons parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CourseLessonListItemPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Lessons' security: - AdminSession: [] /api/admin/course_lessons/{id}/review: post: operationId: adminReviewCourseLesson description: Enqueue AI review for a single lesson. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Course Lessons' security: - AdminSession: [] XsrfToken: [] /api/admin/courses: get: operationId: adminListCourses parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CoursePage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Courses' security: - AdminSession: [] post: operationId: adminCreateCourse parameters: [] responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/Course' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Courses' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CourseInput' security: - AdminSession: [] XsrfToken: [] /api/admin/courses/{id}: get: operationId: adminGetCourse description: |- Get a single course. A missing id surfaces as 404 via the central ent-error handler. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/Course' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Courses' security: - AdminSession: [] put: operationId: adminUpdateCourse parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/Course' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Courses' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CourseInput' security: - AdminSession: [] XsrfToken: [] /api/admin/courses/{id}/review: post: operationId: adminReviewCourse description: Enqueue AI re-review of every current lesson version. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Courses' security: - AdminSession: [] XsrfToken: [] /api/admin/courses/{id}/versions: post: operationId: adminCreateCourseVersion description: Build a new version of the course exercises. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/CourseVersion' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Courses' security: - AdminSession: [] XsrfToken: [] /api/admin/leads: get: operationId: adminListLeads parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/LeadPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Leads' security: - AdminSession: [] /api/admin/lesson_progress: get: operationId: adminListLessonProgress parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/LessonProgressPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Lesson Progress' security: - AdminSession: [] /api/admin/management/role_permissions/{roleId}: get: operationId: adminGetRolePermissions description: The permission matrix for a role. parameters: - name: roleId in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/StaffRoleDetail' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Roles' security: - AdminSession: [] put: operationId: adminUpdateRolePermissions description: Replace the permission matrix for a role. parameters: - name: roleId in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/StaffRoleDetail' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Roles' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RolePermissionsInput' security: - AdminSession: [] XsrfToken: [] /api/admin/management/roles: get: operationId: adminListRoles parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/StaffRolePage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Roles' security: - AdminSession: [] post: operationId: adminCreateRole description: |- Create a role. A duplicate name is a DB unique constraint, surfaced as 409 by the central ent-error handler. parameters: [] responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/StaffRoleDetail' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Roles' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RoleInput' security: - AdminSession: [] XsrfToken: [] /api/admin/management/roles/{id}: get: operationId: adminGetRole description: |- Get a role with its permission matrix. A missing id surfaces as 404 via the central ent-error handler. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/StaffRoleDetail' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Roles' security: - AdminSession: [] put: operationId: adminUpdateRole parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/StaffRoleDetail' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Roles' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RoleInput' security: - AdminSession: [] XsrfToken: [] delete: operationId: adminDeleteRole parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Roles' security: - AdminSession: [] XsrfToken: [] /api/admin/management/staff_members: get: operationId: adminListStaffMembers parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/StaffMemberPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Staff Members' security: - AdminSession: [] post: operationId: adminCreateStaffMember parameters: [] responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/StaffMember' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Staff Members' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/StaffMemberInput' security: - AdminSession: [] XsrfToken: [] /api/admin/management/staff_members/{id}: get: operationId: adminGetStaffMember parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/StaffMember' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Staff Members' security: - AdminSession: [] put: operationId: adminUpdateStaffMember parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/StaffMember' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Staff Members' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/StaffMemberInput' security: - AdminSession: [] XsrfToken: [] delete: operationId: adminDeleteStaffMember parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Staff Members' security: - AdminSession: [] XsrfToken: [] /api/admin/management/users: get: operationId: adminListManagementUsers parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/UserCrudPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Users' security: - AdminSession: [] /api/admin/management/users/{id}: get: operationId: adminGetManagementUser description: |- Get a management user. A missing id surfaces as 404 via the central ent-error handler. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/UserCrud' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Users' security: - AdminSession: [] put: operationId: adminUpdateManagementUser parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/UserCrud' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Users' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserInput' security: - AdminSession: [] XsrfToken: [] /api/admin/messages: get: operationId: adminListMessages parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/LessonAssistantMessagePage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Messages' security: - AdminSession: [] /api/admin/reviews: get: operationId: adminListReviews parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/ReviewPage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Reviews' security: - AdminSession: [] post: operationId: adminCreateReview parameters: [] responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/Review' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Reviews' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ReviewInput' security: - AdminSession: [] XsrfToken: [] /api/admin/reviews/{id}: get: operationId: adminGetReview description: |- Get a single review. A missing id surfaces as 404 via the central ent-error handler, not a typed union member. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/Review' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Reviews' security: - AdminSession: [] put: operationId: adminUpdateReview description: |- Update a review. A missing id surfaces as 404 via the central ent-error handler. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/Review' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Reviews' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ReviewInput' security: - AdminSession: [] XsrfToken: [] delete: operationId: adminDeleteReview parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '403': description: The caller is authenticated but lacks permission for this resource. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - 'Admin: Reviews' security: - AdminSession: [] XsrfToken: [] /api/ai/lessons/{lessonId}/messages: get: operationId: listAssistantMessages description: The assistant chat history for the current user in this lesson. parameters: - name: lessonId in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: type: array items: $ref: '#/components/schemas/LessonAssistantMessage' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Lesson Player security: - UserSession: [] post: operationId: createAssistantMessage description: |- Ask the assistant. Enqueues generation (river job); the reply is delivered out-of-band. 202 = accepted, 429 = rate limited. SSE streaming is TBD. parameters: - name: lessonId in: path required: true schema: type: integer format: int32 responses: '202': description: The request has been accepted for processing, but processing has not yet completed. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Lesson Player requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AssistantMessageInput' security: - UserSession: [] XsrfToken: [] /api/blog_posts: get: operationId: listBlogPosts description: Paginated published blog posts. parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/BlogPostPage' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Blog /api/blog_posts/{id}/likes: post: operationId: likeBlogPost description: Like a post once per signed-in user; a repeat like changes nothing. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/BlogPost' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Blog security: - UserSession: [] XsrfToken: [] /api/blog_posts/{id}/next: get: operationId: getNextBlogPost description: |- The next post to read after this one: the next older published post in the request locale. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/BlogPost' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Blog /api/blog_posts/{slug}: get: operationId: getBlogPost description: A published post in the request locale, by slug, with its page data. parameters: - name: slug in: path required: true schema: type: string responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/BlogPostView' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Blog /api/book: get: operationId: getBook description: The book page's state for whoever is visiting; a visitor is answered too. parameters: [] responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/BookView' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Marketing /api/book/create_request: post: operationId: createBookRequest description: Request the book for the signed-in user. A repeat request changes nothing. parameters: [] responses: '204': description: Empty 204 response for deletes and other content-less successes. '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Marketing security: - UserSession: [] XsrfToken: [] /api/book/download: get: operationId: downloadBook description: |- Download the book: mark the request downloaded and redirect to the PDF in blob storage, or back to the book page when there is no request yet. The browser follows it as a link, so only the session cookie is required — a navigation cannot carry the XSRF header, and a GET changes nothing unsafe. parameters: [] responses: '302': description: Redirection headers: Location: required: true schema: type: string '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Marketing security: - UserSession: [] /api/feeds/yandex_courses: get: operationId: getYandexCoursesFeed description: The same feed at the path the legacy route declares. parameters: [] responses: '200': description: |- The Yandex course catalogue: a YML document (Yandex's own XML dialect) of the ru courses, which Yandex polls to list them in its course results. `bytes` rather than a modelled XML body: ogen has no XML codec and generates a non-JSON response only as a stream (`format: binary`), so the document is built with `encoding/xml` in `internal/feeds` and handed over as a reader. content: application/xml: schema: type: string format: binary default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Feeds /api/feeds/yandex_courses.xml: get: operationId: getYandexCoursesFeedXml description: |- The feed at the address production actually answers on. Legacy routes `/api` with a JSON default format and the action only knows XML, so the bare path has always answered 406 and Yandex reads this one. parameters: [] responses: '200': description: |- The Yandex course catalogue: a YML document (Yandex's own XML dialect) of the ru courses, which Yandex polls to list them in its course results. `bytes` rather than a modelled XML body: ogen has no XML codec and generates a non-JSON response only as a stream (`format: binary`), so the document is built with `encoding/xml` in `internal/feeds` and handed over as a reader. content: application/xml: schema: type: string format: binary default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Feeds /api/language_categories: get: operationId: listPublicCourseCategories description: List the categories of the request locale. parameters: [] responses: '200': description: The request has succeeded. content: application/json: schema: type: array items: $ref: '#/components/schemas/CourseCategory' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Course Categories /api/language_categories/{slug}: get: operationId: getPublicCourseCategory description: A category and the courses it groups. parameters: - name: slug in: path required: true schema: type: string responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CourseCategoryView' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Course Categories /api/languages: get: operationId: listCourses description: List the published course catalog. parameters: [] responses: '200': description: The request has succeeded. content: application/json: schema: type: array items: $ref: '#/components/schemas/CourseCatalogItem' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Courses /api/languages/{courseSlug}/lessons/{slug}: get: operationId: getCourseLesson description: |- Lesson player payload (theory, starter code, tests) by slug, plus the visitor's progress. Reading a lesson never starts it: the frontend preloads routes on hover, so a read with that effect would enroll a learner in every lesson they pointed at (ADR-0012). Theory stays public and indexable for everyone, including a lesson the visitor may not take yet. parameters: - name: courseSlug in: path required: true schema: type: string - name: slug in: path required: true schema: type: string responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CourseLessonView' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Courses /api/languages/{slug}: get: operationId: getCourse description: Course landing page by slug. parameters: - name: slug in: path required: true schema: type: string responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CourseView' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Courses /api/leads: post: operationId: createLead description: Submit a contact request. parameters: [] responses: '201': description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: '#/components/schemas/Lead' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '422': description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. content: application/json: schema: $ref: '#/components/schemas/ValidationError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Leads requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LeadInput' security: - UserSession: [] XsrfToken: [] /api/lessons/{id}/check: post: operationId: checkLesson description: |- Run a submitted solution and record progress. Synchronous to match legacy; revisit as submit + stream for the Docker-job model (see file header). Public on purpose: a guest must be able to submit, and their position travels in the signed cookie rather than in rows. 409 when the lesson is beyond the gate — the submission is refused before anything is run. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: |- A completed check: the exercise outcome, plus the progress cookie a guest's advanced position travels back in. The cookie is absent for a signed-in learner, whose progress is rows rather than a cookie — which is the only place the two paths differ, and the client never has to notice: it reads the same body either way. headers: Set-Cookie: required: false schema: type: array items: type: string content: application/json: schema: $ref: '#/components/schemas/LessonCheckingResponse' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' '409': description: |- The learner has not reached this lesson yet. Sequential progression (ADR-0012) makes a lesson available only when its position in the course's current version is at most one past the furthest lesson they have finished. Distinct from 403: the caller's permissions are fine, their progress is not. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '422': description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. content: application/json: schema: $ref: '#/components/schemas/ValidationError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Lesson Player requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CheckLessonInput' /api/lessons/{id}/start: post: operationId: startLesson description: |- Start a lesson: enroll the learner in its course if they are not enrolled yet, and mark the lesson started. This is the only way progress begins — loading a page never has this effect, because the frontend preloads routes on hover. Idempotent: starting an already-started or already-finished lesson succeeds and changes nothing. 409 when the lesson is beyond the gate. Public, because a guest progresses under the same rule. For them it validates the gate and returns their position without storing anything: their state is the signed cookie, and only a check moves it. parameters: - name: id in: path required: true schema: type: integer format: int32 responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CourseProgress' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' '409': description: |- The learner has not reached this lesson yet. Sequential progression (ADR-0012) makes a lesson available only when its position in the course's current version is at most one past the furthest lesson they have finished. Distinct from 403: the caller's permissions are fine, their progress is not. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Lesson Player /api/locale/switch: get: operationId: switchLocale description: |- Remember the chosen UI locale: on the signed-in user, and in a cookie the site root reads to pick the locale it redirects to. The page navigates itself afterwards. parameters: - name: locale in: query required: true schema: $ref: '#/components/schemas/Locale' explode: false responses: '204': description: A remembered locale choice and the cookie that carries it. headers: Set-Cookie: required: true schema: type: array items: type: string default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Auth /api/magic_links: post: operationId: createMagicLink description: Email a one-time sign-in link. parameters: [] responses: '204': description: Empty 204 response for deletes and other content-less successes. '422': description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. content: application/json: schema: $ref: '#/components/schemas/ValidationError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Auth requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EmailInput' /api/magic_links/{token}: get: operationId: consumeMagicLink description: Consume a magic link token; sets the JWT cookie. parameters: - name: token in: path required: true schema: type: string responses: '200': description: |- A successful sign-in. Cookie headers are part of the HTTP contract so the implementation stays behind the generated ogen seam. headers: Set-Cookie: required: true schema: type: array items: type: string content: application/json: schema: $ref: '#/components/schemas/User' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Auth /api/map: get: operationId: getSitemap description: Everything the sitemap page lists, across the ru and en locales. parameters: [] responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/Sitemap' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Sitemap /api/me: get: operationId: getCurrentUser description: Resolve the current user from the session cookie (for SSR). parameters: - name: Cookie in: header required: false schema: type: string responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/CurrentUser' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Auth /api/my: get: operationId: getMyDashboard description: The signed-in user's course dashboard. parameters: [] responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/MyDashboard' '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - User Area security: - UserSession: [] /api/password/{token}: patch: operationId: updatePassword description: Set a new password using a reset token; signs the user in. parameters: - name: token in: path required: true schema: type: string responses: '200': description: |- A successful sign-in. Cookie headers are part of the HTTP contract so the implementation stays behind the generated ogen seam. headers: Set-Cookie: required: true schema: type: array items: type: string content: application/json: schema: $ref: '#/components/schemas/User' '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' '422': description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. content: application/json: schema: $ref: '#/components/schemas/ValidationError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Auth requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ResetPasswordInput' /api/password/{token}/edit: get: operationId: checkPasswordResetToken description: Validate a reset token before showing the form. parameters: - name: token in: path required: true schema: type: string responses: '204': description: Empty 204 response for deletes and other content-less successes. '404': description: A resource was not found. content: application/json: schema: $ref: '#/components/schemas/NotFoundError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Auth /api/remind_password: post: operationId: createPasswordReminder description: Email a password-reset link. parameters: [] responses: '204': description: Empty 204 response for deletes and other content-less successes. '422': description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. content: application/json: schema: $ref: '#/components/schemas/ValidationError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Auth requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EmailInput' /api/reviews: get: operationId: listPublicReviews description: Paginated published student reviews. parameters: - $ref: '#/components/parameters/ListQuery.page' - $ref: '#/components/parameters/ListQuery.perPage' - $ref: '#/components/parameters/ListQuery.sortField' - $ref: '#/components/parameters/ListQuery.sortOrder' responses: '200': description: The request has succeeded. content: application/json: schema: $ref: '#/components/schemas/ReviewPage' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Reviews /api/session: post: operationId: createSession description: Log in with email + password; sets the JWT cookie. parameters: [] responses: '200': description: |- A successful sign-in. Cookie headers are part of the HTTP contract so the implementation stays behind the generated ogen seam. headers: Set-Cookie: required: true schema: type: array items: type: string content: application/json: schema: $ref: '#/components/schemas/User' '422': description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. content: application/json: schema: $ref: '#/components/schemas/ValidationError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Auth requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SessionInput' delete: operationId: deleteSession description: Log out; clears the JWT cookie. parameters: [] responses: '204': description: A successful sign-out with the expired auth cookies. headers: Set-Cookie: required: true schema: type: array items: type: string '401': description: The request is not authenticated (no/invalid session cookie). content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Auth security: - UserSession: [] XsrfToken: [] /api/users: post: operationId: createUser description: Sign up; sets the JWT cookie on success. parameters: [] responses: '201': description: A newly-created user and the auth cookies issued for that user. headers: Set-Cookie: required: true schema: type: array items: type: string content: application/json: schema: $ref: '#/components/schemas/User' '422': description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. content: application/json: schema: $ref: '#/components/schemas/ValidationError' default: description: |- Default error response shared by every operation. `@error` emits an OpenAPI `default` response, keeping central transport failures typed without enumerating every status on every operation. content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' tags: - Auth requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SignUpInput' components: parameters: ListQuery.page: name: page in: query required: false schema: type: integer format: int32 minimum: 1 explode: false ListQuery.perPage: name: perPage in: query required: false schema: type: integer format: int32 minimum: 1 maximum: 100 explode: false ListQuery.sortField: name: sortField in: query required: false description: Sort field (ransack `sf`). schema: type: string explode: false ListQuery.sortOrder: name: sortOrder in: query required: false description: Sort order (ransack `so`). schema: type: string enum: - asc - desc explode: false schemas: AssistantMessageInput: type: object required: - message - output - userCode properties: message: type: string minLength: 1 output: type: string nullable: true userCode: type: string nullable: true description: A prompt to the in-lesson assistant, carrying the editor context. Attachment: type: object required: - id - url - filename - contentType - byteSize properties: id: type: integer format: int32 url: type: string filename: type: string contentType: type: string byteSize: type: integer format: int64 description: A stored blob (cover image, outcomes image) returned by the uploader. AttachmentUploadForm: type: object properties: file: type: string format: binary required: - file Banner: type: object required: - id - locale - body - url - background - state - startsAt - finishesAt - createdAt properties: id: type: integer format: int32 locale: $ref: '#/components/schemas/BannerLocale' body: type: string url: type: string nullable: true background: $ref: '#/components/schemas/BannerBackground' state: $ref: '#/components/schemas/BannerState' startsAt: type: string format: date-time nullable: true finishesAt: type: string format: date-time nullable: true createdAt: type: string format: date-time description: 'A site banner (legacy: `Banner`).' BannerBackground: type: string enum: - cta_gradient - dark - blue description: Banner display background. BannerInput: type: object required: - state - background - locale - body - url - startsAt - finishesAt properties: state: $ref: '#/components/schemas/BannerState' background: $ref: '#/components/schemas/BannerBackground' locale: $ref: '#/components/schemas/BannerLocale' body: type: string minLength: 1 url: type: string nullable: true startsAt: type: string format: date-time nullable: true finishesAt: type: string format: date-time nullable: true BannerLocale: type: string enum: - ru - en description: Banners only target ru/en today (legacy `BannerLocale`). BannerPage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/Banner' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. BannerState: type: string enum: - draft - published - archived description: Banner publication state. BlogPost: type: object required: - id - creator - name - slug - description - state - locale - url - richBodyHtml - readingTime - likesCount - relatedCourseItemsCount - relatedCourseIds - coverThumbVariant - coverListVariant - coverMainVariant - createdAt properties: id: type: integer format: int32 creator: $ref: '#/components/schemas/User' name: type: string nullable: true slug: type: string nullable: true description: type: string nullable: true state: allOf: - $ref: '#/components/schemas/BlogPostState' nullable: true locale: type: string nullable: true url: type: string richBodyHtml: type: string readingTime: type: integer format: int32 likesCount: type: integer format: int32 relatedCourseItemsCount: type: integer format: int32 relatedCourseIds: type: array items: type: integer format: int32 description: |- Promoted-course ids in display order, so the admin related-courses editor can round-trip the set it writes. coverThumbVariant: type: string nullable: true coverListVariant: type: string nullable: true coverMainVariant: type: string nullable: true createdAt: type: string format: date-time description: 'A blog post (legacy: `BlogPost`).' BlogPostInput: type: object required: - name - slug - description - state - richBody - coverAttachmentId properties: name: type: string nullable: true slug: type: string nullable: true description: type: string nullable: true state: allOf: - $ref: '#/components/schemas/BlogPostState' nullable: true richBody: type: string description: Trusted editor HTML, stored and returned without sanitization. coverAttachmentId: type: integer format: int32 nullable: true description: Attachment id from `POST /admin/attachments`, or null to keep/clear. BlogPostPage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/BlogPost' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. BlogPostRelatedCoursesInput: type: object required: - courseIds properties: courseIds: type: array items: type: integer format: int32 description: The set of courses promoted on a blog post. BlogPostState: type: string enum: - draft - published - archived description: Blog post publication state. BlogPostView: type: object required: - post - recommendedPosts - relatedLandingPages properties: post: $ref: '#/components/schemas/BlogPost' recommendedPosts: type: array items: $ref: '#/components/schemas/BlogPost' description: Two other published posts in the request locale, newest first. relatedLandingPages: type: array items: $ref: '#/components/schemas/CourseCatalogItem' description: |- The main landing page, in the request locale, of each course the post promotes, in the post's display order. description: 'A blog post page: the post plus what the page shows under it.' BookView: type: object required: - requested - url properties: requested: type: boolean description: Whether the signed-in visitor has requested the book, in any state; false for a visitor. url: type: string description: Absolute canonical URL of the book page. description: The free book page (ru marketing funnel, legacy `books#show`). CheckLessonInput: type: object required: - code - versionId properties: code: type: string minLength: 1 versionId: type: integer format: int32 description: The lesson version the code was written against. description: A submitted solution to run against the lesson's tests. Course: type: object required: - id - slug - name - learnAs - readiness - categoryId - currentVersionId - currentVersion - createdAt - enrollmentsCount - lessonsCount - ratingCount - ratingValue - repositoryUrl - hexletProgramLandingPage - coverListVariant - coverThumbVariant properties: id: type: integer format: int32 slug: type: string name: type: string nullable: true learnAs: allOf: - $ref: '#/components/schemas/CourseLearnAs' nullable: true readiness: allOf: - $ref: '#/components/schemas/CourseReadiness' nullable: true categoryId: type: integer format: int32 nullable: true currentVersionId: type: integer format: int32 nullable: true currentVersion: type: object allOf: - $ref: '#/components/schemas/CourseVersion' nullable: true createdAt: type: string format: date-time enrollmentsCount: type: integer format: int32 lessonsCount: type: integer format: int32 ratingCount: type: integer format: int32 ratingValue: type: number format: double repositoryUrl: type: string nullable: true hexletProgramLandingPage: type: string nullable: true coverListVariant: type: string nullable: true coverThumbVariant: type: string nullable: true description: |- A course (legacy: `Language`). Superset read model: the public catalog embeds it, admin course screens read the full shape. CourseCatalogItem: type: object required: - id - slug - header - name - locale - enrollmentsCount - duration - coverUrl - course properties: id: type: integer format: int32 slug: type: string header: type: string nullable: true name: type: string nullable: true locale: type: string nullable: true enrollmentsCount: type: integer format: int32 duration: type: integer format: int32 description: Estimated duration in hours (derived from lesson count). coverUrl: type: string nullable: true description: Course cover image URL; null until assets are re-uploaded. course: $ref: '#/components/schemas/Course' description: |- A catalog entry as shown on the courses index (legacy: `LanguageLandingPageForLists`). Embeds the full course plus derived fields. CourseCategory: type: object required: - id - slug - name - header - description - locale - createdAt properties: id: type: integer format: int32 slug: type: string nullable: true name: type: string nullable: true header: type: string nullable: true description: type: string nullable: true locale: type: string nullable: true createdAt: type: string format: date-time description: 'A course category (legacy: `LanguageCategory` / table `language_categories`).' CourseCategoryInput: type: object required: - name - header - slug - description properties: name: type: string minLength: 1 header: type: string minLength: 1 slug: type: string minLength: 1 description: type: string nullable: true description: Writable fields for creating or updating a course category. CourseCategoryPage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/CourseCategory' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. CourseCategoryView: type: object required: - category - url - landingPages - qnaItems properties: category: $ref: '#/components/schemas/CourseCategory' url: type: string description: The page's absolute legacy URL, for its canonical link. landingPages: type: array items: $ref: '#/components/schemas/CourseCatalogItem' description: |- The category's listed, published landing pages in the request locale whose course is completed, in course order. qnaItems: type: array items: $ref: '#/components/schemas/QnaItem' description: The category's questions and answers, oldest first. description: A category page with the courses it groups. CourseInput: type: object required: - slug - learnAs - readiness - hexletProgramLandingPage - repositoryUrl - coverAttachmentId properties: slug: type: string nullable: true learnAs: allOf: - $ref: '#/components/schemas/CourseLearnAs' nullable: true readiness: allOf: - $ref: '#/components/schemas/CourseReadiness' nullable: true hexletProgramLandingPage: type: string nullable: true repositoryUrl: type: string nullable: true coverAttachmentId: type: integer format: int32 nullable: true description: Attachment id from `POST /admin/attachments`, or null. CourseLandingPage: type: object required: - id - courseId - courseSlug - createdAt - slug - name - main - listed - state - order - footer - footerName - landingPageToRedirectId - metaTitle - metaDescription - header - description - usedInHeader - usedInDescription - outcomesHeader - outcomesDescription - outcomesImage - duration - enrollmentsCount properties: id: type: integer format: int32 courseId: type: integer format: int32 courseSlug: type: string createdAt: type: string format: date-time slug: type: string name: type: string main: type: boolean nullable: true listed: type: boolean nullable: true state: allOf: - $ref: '#/components/schemas/LandingPageState' nullable: true order: type: string nullable: true description: Free-form ordering key (legacy stores it as a string). footer: type: boolean nullable: true description: |- Footer visibility + link caption. Part of the read model so the admin edit form can round-trip the fields it writes. footerName: type: string nullable: true landingPageToRedirectId: type: integer format: int32 nullable: true metaTitle: type: string metaDescription: type: string header: type: string description: type: string usedInHeader: type: string nullable: true usedInDescription: type: string nullable: true outcomesHeader: type: string nullable: true outcomesDescription: type: string nullable: true outcomesImage: type: string nullable: true duration: type: integer format: int32 description: Estimated duration in hours. enrollmentsCount: type: integer format: int32 description: 'A marketing landing page for a course (legacy: `LanguageLandingPage`).' CourseLandingPageInput: type: object required: - courseId - slug - name - main - listed - footer - footerName - state - order - landingPageToRedirectId - metaTitle - metaDescription - header - description - usedInHeader - usedInDescription - outcomesHeader - outcomesDescription - outcomesImageAttachmentId properties: courseId: type: integer format: int32 slug: type: string nullable: true name: type: string nullable: true main: type: boolean nullable: true listed: type: boolean nullable: true footer: type: boolean nullable: true footerName: type: string nullable: true state: allOf: - $ref: '#/components/schemas/LandingPageState' nullable: true order: type: string nullable: true landingPageToRedirectId: type: integer format: int32 nullable: true metaTitle: type: string nullable: true metaDescription: type: string nullable: true header: type: string nullable: true description: type: string nullable: true usedInHeader: type: string nullable: true usedInDescription: type: string nullable: true outcomesHeader: type: string nullable: true outcomesDescription: type: string nullable: true outcomesImageAttachmentId: type: integer format: int32 nullable: true description: Attachment id from `POST /admin/attachments`, or null. CourseLandingPagePage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/CourseLandingPage' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. CourseLearnAs: type: string enum: - first_language - second_language description: How a course is meant to be learned. CourseLesson: type: object required: - course - id - name - slug - locale - naturalOrder - versionId - version - description - instructions - theory - definitions - tips - preparedCode - originalCode - testCode - sourceCodeUrl - createdAt properties: course: $ref: '#/components/schemas/Course' id: type: integer format: int32 name: type: string nullable: true slug: type: string locale: type: string nullable: true naturalOrder: type: integer format: int32 versionId: type: integer format: int32 version: type: integer format: int32 nullable: true description: type: string nullable: true instructions: type: string nullable: true theory: type: string nullable: true definitions: type: array items: $ref: '#/components/schemas/LessonDefinition' tips: type: array items: type: string preparedCode: type: string nullable: true originalCode: type: string nullable: true testCode: type: string nullable: true sourceCodeUrl: type: string nullable: true createdAt: type: string format: date-time description: 'A single lesson (legacy: `LanguageLesson`).' CourseLessonListItem: type: object required: - id - name - description - slug properties: id: type: integer format: int32 name: type: string nullable: true description: type: string nullable: true slug: type: string description: 'A lesson as shown in admin lists (legacy: `LanguageLessonForLists`).' CourseLessonListItemPage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/CourseLessonListItem' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. CourseLessonReview: type: object required: - id - locale - courseId - courseLessonId - courseLessonVersionId - courseLessonVersionInfoId - summary - slug - lessonNaturalOrder - courseSlug - createdAt properties: id: type: integer format: int32 locale: type: string courseId: type: integer format: int32 courseLessonId: type: integer format: int32 courseLessonVersionId: type: integer format: int32 courseLessonVersionInfoId: type: integer format: int32 summary: type: string slug: type: string lessonNaturalOrder: type: integer format: int32 courseSlug: type: string createdAt: type: string format: date-time description: 'An AI-generated lesson review record (legacy: `LanguageLessonReview`).' CourseLessonReviewPage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/CourseLessonReview' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. CourseLessonView: type: object required: - lesson - landingPage - lessons - progress properties: lesson: $ref: '#/components/schemas/CourseLesson' landingPage: type: object allOf: - $ref: '#/components/schemas/CourseLandingPage' nullable: true description: |- The course's landing copy — the same page the course landing read resolves: the one flagged main, otherwise the oldest. The player titles the page with the landing page's name, not the course's own `name`: those are two different strings, and the marketing name is the one a learner recognises. The description of the page is composed from it too, which is why the whole page travels rather than just its name. Null for a course with no landing page, which is a course nobody has written copy for yet — not a reason to fail the read. lessons: type: array items: $ref: '#/components/schemas/CourseLessonListItem' description: |- Every Lesson of the course's current version, in course order — the same list the course landing read returns. The player's navigation tab is this list joined to `progress.lessons` by slug: names and order from here, checks and locks from there. Both screens render from the same two arrays, which is what keeps the Course page's list and the player's from disagreeing. progress: type: object allOf: - $ref: '#/components/schemas/CourseProgress' nullable: true description: |- Where the visitor stands in the course this lesson belongs to — the same payload the course landing read returns, for a signed-in learner and a guest alike. The player reads `lessons[].available` to know whether this one may be taken, and `nextLessonSlug` for where "next" goes. Null only for a course with no built version. description: 'The lesson page: the lesson itself, and where the visitor stands in it.' CoursePage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/Course' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. CourseProgress: type: object required: - state - completion - nextLessonSlug - furthestFinishedPosition - lessons properties: state: allOf: - $ref: '#/components/schemas/EnrollmentState' nullable: true completion: type: integer format: int32 description: The share of the Course's current Lessons finished (0–100). nextLessonSlug: type: string nullable: true description: The first unfinished Lesson in course order — where the learner resumes. furthestFinishedPosition: type: integer format: int32 description: The highest Position finished, 0 when nothing has been. lessons: type: array items: $ref: '#/components/schemas/LessonProgressItem' description: |- A learner's position in one Course, computed against its current Version. Availability travels with it even though it is a pure function of position: a client deriving it would be a second implementation of the gate, free to drift from the server that enforces it. CourseReadiness: type: string enum: - completed - in_development - draft description: Publication/readiness state of a course. CourseVersion: type: object required: - id - result - state - createdAt properties: id: type: integer format: int32 result: type: string nullable: true state: type: string nullable: true createdAt: type: string format: date-time description: A build/version of a course's exercises (legacy `LanguageVersion`). CourseView: type: object required: - course - landingPage - lessons - enrollment - progress properties: course: $ref: '#/components/schemas/Course' landingPage: type: object allOf: - $ref: '#/components/schemas/CourseLandingPage' nullable: true lessons: type: array items: $ref: '#/components/schemas/CourseLessonListItem' enrollment: type: object allOf: - $ref: '#/components/schemas/Enrollment' nullable: true description: The current user's enrollment, when signed in. progress: type: object allOf: - $ref: '#/components/schemas/CourseProgress' nullable: true description: |- Where the visitor stands in this course. Identical in shape for a signed-in learner, whose position is rows, and for a guest, whose position is the signed progress cookie — which is what keeps the client free of any guest/member branch. Null only for a course with no built version, where there is nothing to stand in. description: Everything the public course landing needs in one payload. CurrentUser: type: object required: - user properties: user: type: object allOf: - $ref: '#/components/schemas/User' nullable: true description: The current user resolved from the session cookie (null when anonymous). EmailInput: type: object required: - email properties: email: type: string format: email Enrollment: type: object required: - id - userId - courseId - state - completion - nextLessonName - progress properties: id: type: integer format: int32 userId: type: integer format: int32 courseId: type: integer format: int32 state: allOf: - $ref: '#/components/schemas/EnrollmentState' nullable: true completion: type: integer format: int32 description: "Completion: the share of the course's current lessons finished (0–100)." nextLessonName: type: string nullable: true progress: allOf: - $ref: '#/components/schemas/CourseProgress' description: Where the learner stands, and what they may take next. description: A learner's enrollment in a course (table `language_members`). EnrollmentState: type: string enum: - started - finished description: Enrollment and lesson-progress lifecycle (legacy `MemberState`). FirstVisit: type: object required: - utmSource - utmMedium - utmCampaign - utmContent - utmTerm - landingPage - referrer properties: utmSource: type: string nullable: true utmMedium: type: string nullable: true utmCampaign: type: string nullable: true utmContent: type: string nullable: true utmTerm: type: string nullable: true landingPage: type: string nullable: true description: The full URL the visit landed on, query string included. referrer: type: string nullable: true description: The first visit's traffic source, as recorded by the browser. LandingPageState: type: string enum: - draft - archived - published description: Publication state shared by landing pages. Lead: type: object required: - id - userId - fullName - email - phone - whatsapp - telegram - surveyAnswersData - coursesData - createdAt properties: id: type: integer format: int32 userId: type: integer format: int32 fullName: type: string nullable: true email: type: string nullable: true phone: type: string nullable: true whatsapp: type: string nullable: true telegram: type: string nullable: true surveyAnswersData: type: string nullable: true coursesData: type: string nullable: true createdAt: type: string format: date-time description: 'A sales lead (legacy: `Lead`).' LeadInput: type: object required: - contactMethod - contactValue - ymClientId properties: contactMethod: type: string enum: - telegram - phone - whatsapp contactValue: type: string minLength: 1 ymClientId: type: string nullable: true firstVisit: type: object allOf: - $ref: '#/components/schemas/FirstVisit' nullable: true description: |- Where the visitor first came from. Legacy read this off ahoy's visit; ahoy is not ported (ADR-0015), so the frontend keeps the first visit in a cookie and sends it with the form. Absent when the browser kept no cookie (blocked or cleared). description: 'A lead captured from a marketing form (legacy: `LeadCrud`).' LeadPage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/Lead' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. LessonAssistantMessage: type: object required: - id - role - userId - content - courseSlug - courseLessonSlug - courseLessonName - createdAt properties: id: type: integer format: int32 role: type: string userId: type: integer format: int32 nullable: true content: type: string courseSlug: type: string courseLessonSlug: type: string courseLessonName: type: string createdAt: type: string format: date-time description: 'One message in the in-lesson AI chat (legacy: `AiMessage`).' LessonAssistantMessagePage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/LessonAssistantMessage' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. LessonCheckingResponse: type: object required: - passed - output - result - status - lessonHasBeenFinished - courseHasBeenFinished properties: passed: type: boolean output: type: string result: type: string enum: - passed - failed - failed-infinity status: type: integer format: int32 lessonHasBeenFinished: type: boolean courseHasBeenFinished: type: boolean description: 'Result of running a submitted solution (legacy: `LessonCheckingResponse`).' LessonDefinition: type: object required: - name - description properties: name: type: string description: type: string description: A lesson definition entry (name + description pair). LessonProgress: type: object required: - id - userId - state - messagesCount - createdAt - courseSlug - courseLessonSlug - courseLessonName properties: id: type: integer format: int32 userId: type: integer format: int32 state: $ref: '#/components/schemas/EnrollmentState' messagesCount: type: integer format: int32 nullable: true createdAt: type: string format: date-time courseSlug: type: string courseLessonSlug: type: string courseLessonName: type: string description: |- A learner's progress through one lesson (legacy: `LanguageLessonMember`, table `language_lesson_members`). LessonProgressItem: type: object required: - slug - position - finished - available properties: slug: type: string position: type: integer format: int32 description: Position in the current Version, 1..N across the whole Course. finished: type: boolean available: type: boolean description: Whether the gate lets the learner take it (ADR-0012). description: 'Per-Lesson state for the course and lesson pages: locks and checkmarks.' LessonProgressPage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/LessonProgress' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. Locale: type: string enum: - ru - en - es description: UI locale (the legacy locale set). The frontend `Locale` type is generated from this. MyDashboard: type: object required: - startedEnrollments - finishedEnrollments - landingPagesByCourseId properties: startedEnrollments: type: array items: $ref: '#/components/schemas/Enrollment' finishedEnrollments: type: array items: $ref: '#/components/schemas/Enrollment' landingPagesByCourseId: type: object additionalProperties: $ref: '#/components/schemas/CourseCatalogItem' description: Landing page (catalog item) keyed by course id, for each enrollment. description: "The signed-in user's dashboard (legacy: `MyController#show`)." NotFoundError: type: object required: - message properties: message: type: string description: A resource was not found. PermissionResource: type: string enum: - blog_posts - banners - reviews - leads - messages - language_categories - language_lessons - language_lesson_reviews - language_lesson_members - languages - language_landing_pages description: Admin resources a staff role can be granted permissions on. ProblemDetails: type: object required: - type - title - status properties: type: type: string description: URI identifying the problem type. `about:blank` means the HTTP status. title: type: string description: Short, localized summary for this problem type. status: type: integer format: int32 description: HTTP status code generated for this occurrence. detail: type: string description: Optional occurrence-specific explanation that is safe for clients. instance: type: string description: Optional URI identifying this particular occurrence. description: |- RFC 9457 problem details returned by the transport when request decoding, authorization, persistence, or an unexpected server failure prevents an operation from producing its declared domain response. ProfileInput: type: object required: - firstName - lastName properties: firstName: type: string allOf: - $ref: '#/components/schemas/ProfileName' nullable: true lastName: type: string allOf: - $ref: '#/components/schemas/ProfileName' nullable: true description: 'Profile edit form (legacy: `UserProfileForm`).' ProfileName: type: string maxLength: 40 pattern: ^[^`!@#$%\^&*+=]*$ description: |- A name on the profile (legacy `User` validations on first/last name): at most 40 characters, none of the characters in the pattern; blank is allowed. QnaItem: type: object required: - id - question - answer properties: id: type: integer format: int32 question: type: string answer: type: string description: A Q&A entry attached to a category or landing page (legacy QnA item). QnaItemInput: type: object required: - question - answer properties: question: type: string minLength: 1 answer: type: string minLength: 1 ResetPasswordInput: type: object required: - password properties: password: type: string minLength: 6 description: 'Password reset submission (legacy: `UserPassword`).' Review: type: object required: - id - user - course - userId - courseId - body - firstName - lastName - fullName - state - locale - pinned - createdAt properties: id: type: integer format: int32 user: $ref: '#/components/schemas/User' course: $ref: '#/components/schemas/Course' userId: type: integer format: int32 courseId: type: integer format: int32 body: type: string nullable: true firstName: type: string nullable: true lastName: type: string nullable: true fullName: type: string nullable: true state: allOf: - $ref: '#/components/schemas/ReviewState' nullable: true locale: allOf: - $ref: '#/components/schemas/ReviewLocale' nullable: true pinned: type: boolean nullable: true description: |- Stored for the admin edit form to round-trip; it does not affect the public list, which legacy ordered newest first only. createdAt: type: string format: date-time description: 'A student review/testimonial (legacy: `Review`).' ReviewInput: type: object required: - state - pinned - courseId - userId - body - firstName - lastName properties: state: allOf: - $ref: '#/components/schemas/ReviewState' nullable: true pinned: type: boolean nullable: true courseId: type: integer format: int32 nullable: true userId: type: integer format: int32 nullable: true body: type: string nullable: true firstName: type: string nullable: true lastName: type: string nullable: true ReviewLocale: type: string enum: - ru - en description: Reviews only target ru/en today (legacy `ReviewLocale`). ReviewPage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/Review' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. ReviewState: type: string enum: - draft - published - archived description: Review publication state. RoleInput: type: object required: - name - description properties: name: type: string minLength: 1 description: type: string nullable: true RolePermissionInput: type: object required: - resource - canIndex - canCreate - canUpdate - canDestroy properties: resource: $ref: '#/components/schemas/PermissionResource' canIndex: type: boolean canCreate: type: boolean canUpdate: type: boolean canDestroy: type: boolean description: One permission row to set on a role. RolePermissionsInput: type: object required: - permissions properties: permissions: type: array items: $ref: '#/components/schemas/RolePermissionInput' description: The full permission matrix submitted for a role. SessionInput: type: object required: - email - password properties: email: type: string format: email password: type: string minLength: 1 description: Email + password login. SignUpInput: type: object required: - firstName - email - password properties: firstName: type: string nullable: true email: type: string format: email password: type: string minLength: 6 description: 'Sign-up form (legacy: `UserSignUpForm`).' Sitemap: type: object required: - landingPages - blogPosts - categories properties: landingPages: type: array items: $ref: '#/components/schemas/SitemapCourseLandingPage' blogPosts: type: array items: $ref: '#/components/schemas/SitemapBlogPost' categories: type: array items: $ref: '#/components/schemas/CourseCategory' description: |- The rows behind the HTML sitemap page (legacy `HomeController#sitemap`, `/ru/map`). Flat lists carrying their own locale; the page groups them. Landing pages come already narrowed to those whose course has lessons in the page's locale: legacy sent the lessons only for that check (their list on the page was commented out), so the filter runs in SQL instead of shipping every lesson. SitemapBlogPost: type: object required: - id - name - slug - locale properties: id: type: integer format: int32 name: type: string slug: type: string locale: $ref: '#/components/schemas/Locale' description: 'Minimal blog-post row for the sitemap (legacy: `SitemapBlogPost`).' SitemapCourseLandingPage: type: object required: - id - courseId - slug - header - locale properties: id: type: integer format: int32 courseId: type: integer format: int32 slug: type: string header: type: string locale: $ref: '#/components/schemas/Locale' description: 'Minimal landing-page row for the sitemap (legacy: `LanguageSitemapLandingPage`).' StaffMember: type: object required: - id - userId - roleId - user - role - allowedLocales properties: id: type: integer format: int32 userId: type: integer format: int32 roleId: type: integer format: int32 user: $ref: '#/components/schemas/User' role: $ref: '#/components/schemas/StaffRole' allowedLocales: type: array items: type: string description: 'A staff member: a user bound to a role (legacy: `StaffMember`).' StaffMemberInput: type: object required: - userId - roleId - allowedLocales properties: userId: type: integer format: int32 roleId: type: integer format: int32 allowedLocales: type: array items: type: string StaffMemberPage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/StaffMember' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. StaffRole: type: object required: - id - name - description - permissionsCount - createdAt properties: id: type: integer format: int32 name: type: string description: type: string nullable: true permissionsCount: type: integer format: int32 createdAt: type: string format: date-time description: 'A staff role (legacy: `StaffRole`).' StaffRoleDetail: type: object required: - id - name - description - permissions properties: id: type: integer format: int32 name: type: string description: type: string nullable: true permissions: type: array items: $ref: '#/components/schemas/StaffRolePermission' description: 'A staff role with its full permission matrix (legacy: `StaffRoleCrud`).' StaffRolePage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/StaffRole' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. StaffRolePermission: type: object required: - id - roleId - resource - canIndex - canCreate - canUpdate - canDestroy properties: id: type: integer format: int32 roleId: type: integer format: int32 resource: $ref: '#/components/schemas/PermissionResource' canIndex: type: boolean canCreate: type: boolean canUpdate: type: boolean canDestroy: type: boolean description: "One resource's permission bits for a role (legacy: `StaffRolePermission`)." User: type: object required: - id - firstName - lastName - name - email - admin - canAccessAdmin - assistantMessagesCount - createdAt - createdAtAsTimestamp - type properties: id: type: integer format: int32 firstName: type: string nullable: true lastName: type: string nullable: true name: type: string nullable: true email: type: string nullable: true admin: type: boolean nullable: true canAccessAdmin: type: boolean assistantMessagesCount: type: integer format: int32 nullable: true createdAt: type: string format: date-time createdAtAsTimestamp: type: integer format: int64 nullable: true type: type: string enum: - user description: 'A user (legacy: `User`). Passwords are never part of a response.' UserCrud: type: object required: - id - email - firstName - lastName - admin properties: id: type: integer format: int32 email: type: string nullable: true firstName: type: string nullable: true lastName: type: string nullable: true admin: type: boolean nullable: true description: 'A user row as shown in admin lists/forms (legacy: `UserCrud`).' UserCrudPage: type: object required: - items - total - page - perPage properties: items: type: array items: $ref: '#/components/schemas/UserCrud' total: type: integer format: int32 description: Total rows across all pages (before pagination). page: type: integer format: int32 description: 1-based page number this response represents. perPage: type: integer format: int32 description: Page size used for this response. description: |- A page of results. Generic envelope reused by every admin list so the CRUD engine (TanStack Table) can read pagination uniformly. UserInput: type: object required: - email - firstName - lastName - admin properties: email: type: string format: email firstName: type: string nullable: true lastName: type: string nullable: true admin: type: boolean nullable: true ValidationError: type: object required: - errors properties: errors: type: object additionalProperties: type: array items: type: string description: |- Field-level validation errors, keyed by field name (each value is the list of messages for that field). Returned when a write fails validation — including constraints the schema cannot express, like uniqueness. securitySchemes: UserSession: type: apiKey in: cookie name: JWT XsrfToken: type: apiKey in: header name: X-XSRF-TOKEN AdminSession: type: apiKey in: cookie name: JWT