{
  "openapi": "3.1.1",
  "info": {
    "title": "Manabu2 API",
    "description": "Read and write access to Manabu2 learning content.\n\n**Authentication.** Every endpoint requires a bearer token. Get one from `POST /api/auth/login`, refresh it with `POST /api/auth/refresh`.\n\n**What you can see.** Catalog reads return published content that is either public, or owned by an organization you belong to. Anything else responds 404 — the API does not distinguish \"does not exist\" from \"not yours\".\n\n**Paging.** List endpoints take `page` (1-based) and `pageSize` (max 100), and return `{ items, page, pageSize, totalItems, totalPages }`.\n\n**Scopes.** Every endpoint requires one. A learner token carries `read:catalog read:content read:progress write:progress`; an API key carries exactly what it was granted. Missing a scope gives 403 `insufficient_scope`.",
    "version": "v1"
  },
  "servers": [
    {
      "url": "https://api.manabu2.com/"
    }
  ],
  "paths": {
    "/api/v1/me/link": {
      "post": {
        "tags": [
          "AccountLink"
        ],
        "summary": "Adds an email and password so this account can be recovered.",
        "description": "Call this from a \"save your progress\" screen once the student has something\nworth keeping. Afterwards they can sign in on a new device with\n`POST /api/v1/auth/login` and keep all of it.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccountLinkRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/AccountLinkRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/AccountLinkRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountLinkResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ai/materials/generate": {
      "post": {
        "tags": [
          "Ai"
        ],
        "summary": "Generates or rewrites lesson material, optionally from an uploaded file.",
        "description": "Multipart form, not JSON, because it accepts a file. Billed per call and rate limited separately from reads.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "allOf": [
                  {
                    "type": "object",
                    "properties": {
                      "action": {
                        "type": "string"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "properties": {
                      "prompt": {
                        "type": "string"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "properties": {
                      "existingRaw": {
                        "type": "string"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "properties": {
                      "courseTitle": {
                        "type": "string"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "properties": {
                      "courseDescription": {
                        "type": "string"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "properties": {
                      "sectionTitle": {
                        "type": "string"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "properties": {
                      "lessonTitle": {
                        "type": "string"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "properties": {
                      "uploadFile": {
                        "$ref": "#/components/schemas/IFormFile"
                      }
                    }
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/api-keys": {
      "get": {
        "tags": [
          "ApiKeys"
        ],
        "summary": "Lists this organization's keys, including revoked ones.",
        "description": "Never returns key material — only the prefix and last 4 characters.",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ApiKeySummaryDto"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "ApiKeys"
        ],
        "summary": "Issues a new key and returns the secret — the only time it is shown.",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyCreatedDto"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{organizationId}/api-keys/{apiKeyId}": {
      "delete": {
        "tags": [
          "ApiKeys"
        ],
        "summary": "Revokes a key immediately.",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "apiKeyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/auth/login": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Signs in with email and password.",
        "description": "Returns an access token and the refresh token that renews it.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/auth/refresh": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Exchanges a refresh token for a new access + refresh pair.",
        "description": "Refresh tokens are single-use. Presenting one twice is treated as theft:\nthe whole session is revoked and both callers must sign in again. Store the\ntoken returned here and discard the one you sent.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefreshRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/RefreshRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/RefreshRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/auth/logout": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Signs out, ending the session the refresh token belongs to.",
        "description": "Idempotent, and deliberately anonymous: signing out must work even once the\naccess token has expired. Always 204, so it cannot be used to probe whether\na token exists.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefreshRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/RefreshRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/RefreshRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        }
      }
    },
    "/api/v1/auth/logout-all": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Ends every session for the signed-in user, on all devices.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { }
            }
          }
        }
      }
    },
    "/api/v1/auth/sessions": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Lists the signed-in user's active sessions, newest first.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SessionDto"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/courses": {
      "get": {
        "tags": [
          "Courses"
        ],
        "summary": "Lists courses the caller can see.",
        "description": "**Requires scope:** `read:catalog`",
        "parameters": [
          {
            "name": "culture",
            "in": "query",
            "description": "Filter by content language, e.g. `ja-JP` or `en-US`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "organizationId",
            "in": "query",
            "description": "Restrict to one organization.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Free-text match on title and description.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "1-based page number.",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "description": "Items per page. Capped at 100.",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedResponseOfCourseSummaryDto"
                }
              }
            }
          }
        },
        "x-required-scope": "read:catalog"
      }
    },
    "/api/v1/courses/{id}": {
      "get": {
        "tags": [
          "Courses"
        ],
        "summary": "Gets one course with its sections and published lessons.",
        "description": "Returns 404 both when the course does not exist and when the caller may not see it.\n\n**Requires scope:** `read:catalog`",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseDetailDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "read:catalog"
      }
    },
    "/api/v1/lessons/{id}": {
      "get": {
        "tags": [
          "Lessons"
        ],
        "summary": "Gets one lesson, including its quiz without the answer key.",
        "description": "**Requires scope:** `read:content`",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonDetailDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "read:content"
      }
    },
    "/api/v1/materials/{id}": {
      "get": {
        "tags": [
          "Materials"
        ],
        "summary": "Gets one material.",
        "description": "**Requires scope:** `read:content`",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialDetailDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "read:content"
      }
    },
    "/api/v1/paths": {
      "get": {
        "tags": [
          "Paths"
        ],
        "summary": "Lists learning paths the caller can see.",
        "description": "**Requires scope:** `read:catalog`",
        "parameters": [
          {
            "name": "culture",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "organizationId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedResponseOfPathSummaryDto"
                }
              }
            }
          }
        },
        "x-required-scope": "read:catalog"
      }
    },
    "/api/v1/paths/{id}": {
      "get": {
        "tags": [
          "Paths"
        ],
        "summary": "Gets one path with its courses, in the order they should be taken.",
        "description": "**Requires scope:** `read:catalog`",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PathDetailDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "read:catalog"
      }
    },
    "/api/v1/certificates": {
      "get": {
        "tags": [
          "Certificates"
        ],
        "summary": "Lists certificates belonging to the caller's organizations.",
        "description": "Certificates are organization property — there is no public catalog of them.\n\n**Requires scope:** `read:catalog`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedResponseOfCertificateDto"
                }
              }
            }
          }
        },
        "x-required-scope": "read:catalog"
      }
    },
    "/api/v1/devices/register": {
      "post": {
        "tags": [
          "Devices"
        ],
        "summary": "Registers an app installation and returns its learner's tokens.",
        "description": "    Call this once, on first launch. Store `deviceSecret` in the Keychain\n    or Keystore — it is shown only here, and it is what proves this installation\n    owns the account on any later call.\n    Registering twice with the same deviceId returns the same learner\nwith a fresh secret, rather than a second empty account.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceRegisterRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceRegisterRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceRegisterRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceRegistrationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/devices/token": {
      "post": {
        "tags": [
          "Devices"
        ],
        "summary": "Exchanges a device secret for a fresh set of tokens.",
        "description": "For when the refresh token is gone but the secret survives — an app\nreinstall that kept its keystore entry, or a token cache cleared on upgrade.\nPrefer `POST /api/v1/auth/refresh` while you still hold a refresh token.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceTokenRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceTokenRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceTokenRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "get": {
        "tags": [
          "Mcp"
        ],
        "summary": "Opens the Model Context Protocol stream (server-sent events).",
        "description": "Requires a credential like every other endpoint; see /{culture}/mcp for how to connect.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "Mcp"
        ],
        "summary": "The Streamable HTTP transport: one POST, one response.",
        "description": "    Also serves the plain one-shot JSON call this endpoint has always accepted.\n    Which of the two a caller gets is decided entirely by its `Accept`\n    header, so nothing that works today changes.\n    Alongside the older GET /mcp stream rather than replacing it. Claude's\nconnector uses that path now, and a transport upgrade that disconnects the\nclients already working is not an upgrade.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonElement"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonElement"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/JsonElement"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "tags": [
          "Mcp"
        ],
        "summary": "Ends a session.",
        "description": "Part of the transport, and answered rather than 405'd even though there is\nnothing to release: a client that cannot close a session cleanly logs an\nerror on every shutdown.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/mcp/message": {
      "post": {
        "tags": [
          "Mcp"
        ],
        "summary": "Sends a JSON-RPC message on an established MCP stream.",
        "parameters": [
          {
            "name": "sessionId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonElement"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonElement"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/JsonElement"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/me": {
      "delete": {
        "tags": [
          "MeAccount"
        ],
        "summary": "Closes the signed-in learner's account.",
        "description": "    The identity is destroyed: email and profile are cleared, the address is\n    released for reuse, the password and every token are gone, 志望校 are\n    deleted, and the device registrations are removed so the next launch creates\n    a genuinely new learner rather than recovering this one. Learning history\n    stays, no longer attached to anybody.\n    There is no confirmation parameter and no undo. The student has already\nconfirmed in the app, and asking twice in two different places is how people\nlearn to click through prompts.\n    Requires write:progress — the scope a learner's own app already\ncarries. Deliberately not a new scope: a token that can record a learner's\nwork is already acting as them, and inventing a scope for this would mean\nevery existing ワカルート install had to re-authorize before it could offer\nthe button App Review requires.\n\n**Requires scope:** `write:progress`",
        "responses": {
          "204": {
            "description": "Closed.",
            "content": {
              "application/json": { }
            }
          },
          "409": {
            "description": "A member of an organization. Their admin must do it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:progress"
      },
      "get": {
        "tags": [
          "Me"
        ],
        "summary": "Returns the signed-in learner and the organizations they belong to.",
        "description": "    The organizations are named, not described: an id, a display name, and the\n    caller's own roles. Anything about the organization as a business, or about\n    anyone else in it, is somewhere that asks for a scope saying so.\n    This returned the domain OrganizationDto until t-d1bea74 — the whole\nmember list, with every other learner's id and role flags, their hourly rate,\ntitle and department, the invitation token that accepts an invitation, and\nthe subscription plan and seat count alongside. Harmless while the only\ncallers were an organization's own staff; every ワカルート install is a\ndevice-registered learner in one shared organization, so it became every\nstudent's phone downloading a roster of every other student.\n\n**Requires scope:** `read:progress`",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "read:progress"
      }
    },
    "/api/v1/me/progress": {
      "get": {
        "tags": [
          "Me"
        ],
        "summary": "Progress across every course the learner has started.",
        "description": "**Requires scope:** `read:progress`",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CourseProgressDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      }
    },
    "/api/v1/me/progress/{courseId}": {
      "get": {
        "tags": [
          "Me"
        ],
        "summary": "Progress for one course, broken down by lesson.",
        "description": "Returns 404 both when the course does not exist and when the caller may not see it.\n\n**Requires scope:** `read:progress`",
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseProgressDetailDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      }
    },
    "/api/v1/me/paths": {
      "get": {
        "tags": [
          "Me"
        ],
        "summary": "Learning paths assigned to the learner.",
        "description": "**Requires scope:** `read:progress`",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MyPathDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      }
    },
    "/api/v1/me/certificates": {
      "get": {
        "tags": [
          "Me"
        ],
        "summary": "Certificates the learner has been awarded.",
        "description": "**Requires scope:** `read:progress`",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MyCertificateDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      }
    },
    "/api/v1/me/assignments": {
      "get": {
        "tags": [
          "Me"
        ],
        "summary": "Courses assigned to the learner, directly or through a path.",
        "description": "**Requires scope:** `read:progress`",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CourseSummaryDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      }
    },
    "/api/v1/me/tests": {
      "get": {
        "tags": [
          "Me"
        ],
        "summary": "Tests available to the learner, with their latest result on each.",
        "description": "Answer keys are not in TestSummaryDto, and neither are the\nquestions — this is the list of what there is to sit. Fetch one from\n`GET /api/v1/tests/{id}` to actually take it.\n\n**Requires scope:** `read:progress`",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TestSummaryDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      }
    },
    "/api/v1/me/quiz-attempts": {
      "get": {
        "tags": [
          "Me"
        ],
        "summary": "The learner's own past quiz attempts, newest first.",
        "description": "    Every attempt, not just the best or the latest: a lesson can be sat any\n    number of times, and the climb from 40% to 90% is the part worth showing a\n    student or their parents.\n    from and to are Japanese calendar\ndates and both inclusive, so from=2026-07-01&amp;to=2026-07-01 is\nthat one day. Omit either for an open end.\n\n**Requires scope:** `read:progress`",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "Inclusive start date (yyyy-MM-dd, Asia/Tokyo).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Inclusive end date (yyyy-MM-dd, Asia/Tokyo).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Rows to return, newest first. 1–500, default 200.",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/QuizAttemptDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      }
    },
    "/api/v1/me/test-attempts": {
      "get": {
        "tags": [
          "Me"
        ],
        "summary": "The learner's own past test sittings, newest first.",
        "description": "`GET /me/tests` gives the latest result per test; this gives the run of\nthem. Same date and limit rules as `/me/quiz-attempts`, and the same\nanswer on timing — `isWithinTimeLimit` is null when there was no limit\nor no elapsed time reported.\n\n**Requires scope:** `read:progress`",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "Inclusive start date (yyyy-MM-dd, Asia/Tokyo).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Inclusive end date (yyyy-MM-dd, Asia/Tokyo).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Rows to return, newest first. 1–500, default 200.",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TestAttemptHistoryDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      }
    },
    "/api/v1/lessons/{id}/view": {
      "post": {
        "tags": [
          "LessonProgress"
        ],
        "summary": "Records that the learner opened the lesson. Safe to call on every open.",
        "description": "**Requires scope:** `write:progress`",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonCompletionDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:progress"
      }
    },
    "/api/v1/lessons/{id}/complete": {
      "post": {
        "tags": [
          "LessonProgress"
        ],
        "summary": "Marks the lesson complete and returns the course's updated progress.",
        "description": "Idempotent — completing an already-complete lesson is not an error.\n\n**Requires scope:** `write:progress`",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonCompletionDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:progress"
      }
    },
    "/api/v1/lessons/{id}/quiz/submit": {
      "post": {
        "tags": [
          "LessonProgress"
        ],
        "summary": "Submits answers to the lesson's quiz and returns the graded result.",
        "description": "Grading happens on the server; the answer key is never sent to the client.\nPassing the quiz also marks the lesson complete.\n\n**Requires scope:** `write:progress`",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuizSubmissionRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/QuizSubmissionRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/QuizSubmissionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuizResultDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:progress"
      }
    },
    "/api/v1/lessons/{id}/feedback": {
      "post": {
        "tags": [
          "LessonProgress"
        ],
        "summary": "Records whether the lesson made sense to the learner.",
        "description": "    Two-valued rather than a five-point scale: thirteen-year-olds cluster on the\n    middle of a scale, so the average never moves and nobody learns which lesson\n    to fix. A rate can be sorted, and the top of that list is the work.\n    One verdict per learner per lesson. Answering again replaces the previous\none, so re-reading and changing your mind counts once. Send an\nIdempotency-Key if the client retries — the replacement is already\nsafe to repeat, but the key saves the round trip.\n    comment is optional and written by children: it is kept for twelve\nmonths and removed when the account closes, while the verdict itself stays.\n\n**Requires scope:** `write:progress`",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LessonFeedbackRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/LessonFeedbackRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/LessonFeedbackRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonFeedbackReceiptDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:progress"
      }
    },
    "/api/v1/me/goals": {
      "get": {
        "tags": [
          "MeGoals"
        ],
        "summary": "The learner's goals, 第一志望 first.",
        "description": "**Requires scope:** `read:progress`",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "description": "Optional filter: `high_school`, `university`, `company`, `certification`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GoalListDto"
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      },
      "put": {
        "tags": [
          "MeGoals"
        ],
        "summary": "Replaces one kind of goal outright with the given ordered list.",
        "description": "Replace rather than patch, so reorder and remove need no extra verbs and two\ndevices saving at once cannot interleave into a list with a duplicate\nposition. Array order is the preference order; a `rank` field is not\naccepted, because a client able to send both would eventually send two that\ndisagree.\n\n**Requires scope:** `write:progress`",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GoalListWriteDto"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/GoalListWriteDto"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/GoalListWriteDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GoalListDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:progress"
      }
    },
    "/api/v1/me/target-schools": {
      "get": {
        "tags": [
          "MeGoals"
        ],
        "summary": "The learner's 志望校, 第一志望 first.",
        "description": "The ワカルート client's route. Identical to `/me/goals?type=high_school`.\n\n**Requires scope:** `read:progress`",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GoalListDto"
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      },
      "put": {
        "tags": [
          "MeGoals"
        ],
        "summary": "Replaces the learner's 志望校 list.",
        "description": "**Requires scope:** `write:progress`",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GoalListWriteDto"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/GoalListWriteDto"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/GoalListWriteDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GoalListDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:progress"
      }
    },
    "/api/v1/me/study-sessions/start": {
      "post": {
        "tags": [
          "MeStudy"
        ],
        "summary": "Starts the clock.",
        "description": "Closes out whatever was already running for this learner — one clock per\nperson. A student who starts maths having left an English session running an\nhour ago should not have both counting.\n\n**Requires scope:** `write:progress`",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/StartStudySessionDto"
                  }
                ]
              }
            },
            "text/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/StartStudySessionDto"
                  }
                ]
              }
            },
            "application/*+json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/StartStudySessionDto"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudySessionDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:progress"
      }
    },
    "/api/v1/me/study-sessions/{sessionId}/stop": {
      "post": {
        "tags": [
          "MeStudy"
        ],
        "summary": "Stops a running session.",
        "description": "Stopping one that is already stopped returns it unchanged rather than\nerroring — a client retrying after a timeout is asking whether it is\nstopped, and the honest answer is yes. A session left running past\nTimeSpan StudySessionService.MaxSessionLength comes back\n`abandoned` with a duration of zero: stopping it hours later does not\nmake the hours real.\n\n**Requires scope:** `write:progress`",
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudySessionDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:progress"
      }
    },
    "/api/v1/me/study-sessions": {
      "post": {
        "tags": [
          "MeStudy"
        ],
        "summary": "Records a session that already finished, for offline replay.",
        "description": "**Requires scope:** `write:progress`",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/RecordStudySessionDto"
                  }
                ]
              }
            },
            "text/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/RecordStudySessionDto"
                  }
                ]
              }
            },
            "application/*+json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/RecordStudySessionDto"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudySessionDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:progress"
      },
      "get": {
        "tags": [
          "MeStudy"
        ],
        "summary": "The learner's sessions over a range, most recent first.",
        "description": "**Requires scope:** `read:progress`",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "yyyy-MM-dd. Defaults to 29 days ago.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "yyyy-MM-dd. Defaults to today in Japan.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudySessionListDto"
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      }
    },
    "/api/v1/me/study-summary": {
      "get": {
        "tags": [
          "MeStudy"
        ],
        "summary": "A dense series of how much was studied per day, or per month.",
        "description": "Dense including zero days, so the client does not have to know how many days\nare in each month or where the range ends — the two things a client filling\ngaps itself reliably gets wrong.\n\n**Requires scope:** `read:progress`",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "granularity",
            "in": "query",
            "description": "`day` (default) or `month`.",
            "schema": {
              "type": "string",
              "default": "day"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/StudySummaryDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      }
    },
    "/api/v1/me/study-streak": {
      "get": {
        "tags": [
          "MeStudy"
        ],
        "summary": "Consecutive days studied.",
        "description": "Computed from stored history rather than from anything the handset keeps, so\nit survives a device change — which, over the two to three years a 中1\nstudent uses this, it will need to. Today counts as part of the streak until\nthe day is over: a student who has not studied yet this morning has not lost\nyesterday's run.\n\n**Requires scope:** `read:progress`",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudyStreakDto"
                }
              }
            }
          }
        },
        "x-required-scope": "read:progress"
      }
    },
    "/api/v1/oauth/token": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "summary": "Exchanges an authorization code for an access and refresh token.",
        "description": "Send the same `redirect_uri` you sent to `/connect/authorize`, and\nthe `code_verifier` whose challenge you sent there. Both are checked.\nAccepts form encoding, as the OAuth spec requires, as well as JSON.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "tags": [
          "OAuthDiscovery"
        ],
        "summary": "Tells a client which authorization server protects this API (RFC 9728).",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { }
            }
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "tags": [
          "OAuthDiscovery"
        ],
        "summary": "Describes this authorization server (RFC 8414).",
        "description": "    The issuer is the API host even though the authorize endpoint sits on the\n    web host. That split is deliberate rather than untidy: authorization means\n    showing a person a sign-in page and a consent screen, which needs the\n    Identity cookie and the Razor views — both of which live in the web app.\n    The token endpoint has no user interface and belongs with the API.\n    Only S256 is advertised. Plain PKCE is no protection at all against an\nattacker who can see the authorization request, and advertising it invites\na client to use it.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { }
            }
          }
        }
      }
    },
    "/api/v1/oauth/register": {
      "post": {
        "tags": [
          "OAuthRegistration"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientRegistrationRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientRegistrationRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/ClientRegistrationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": { }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{organizationId}/ai/course-outline": {
      "post": {
        "tags": [
          "OrganizationAi"
        ],
        "summary": "Drafts a course outline: sections and the lessons within them.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AiOutlineRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/AiOutlineRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/AiOutlineRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AiDraftDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/ai/lesson-body": {
      "post": {
        "tags": [
          "OrganizationAi"
        ],
        "summary": "Drafts the body of one lesson, in markdown.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AiLessonBodyRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/AiLessonBodyRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/AiLessonBodyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AiDraftDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/ai/quiz-questions": {
      "post": {
        "tags": [
          "OrganizationAi"
        ],
        "summary": "Drafts quiz questions for a lesson, with the answer key.",
        "description": "Returns which option is correct, which is why this needs\n`write:content` like the rest of authoring — the output is an answer\nkey, and it must not be reachable by anyone who could be sitting the quiz.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AiQuizRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/AiQuizRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/AiQuizRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AiDraftDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/overview": {
      "get": {
        "tags": [
          "OrganizationAnalytics"
        ],
        "summary": "The organization at a glance.",
        "description": "**Requires scope:** `read:reports`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "days",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrgOverviewDto"
                }
              }
            }
          }
        },
        "x-required-scope": "read:reports"
      }
    },
    "/api/v1/organizations/{organizationId}/reports/completion": {
      "get": {
        "tags": [
          "OrganizationAnalytics"
        ],
        "summary": "Completion for every course in the organization.",
        "description": "**Requires scope:** `read:reports`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CourseCompletionDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:reports"
      }
    },
    "/api/v1/organizations/{organizationId}/reports/departments": {
      "get": {
        "tags": [
          "OrganizationAnalytics"
        ],
        "summary": "Completion broken down by department.",
        "description": "**Requires scope:** `read:reports`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DepartmentCompletionDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:reports"
      }
    },
    "/api/v1/organizations/{organizationId}/reports/trend": {
      "get": {
        "tags": [
          "OrganizationAnalytics"
        ],
        "summary": "Lessons completed per month, for a trend line.",
        "description": "**Requires scope:** `read:reports`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "months",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 12
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CompletionPointDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:reports"
      }
    },
    "/api/v1/organizations/{organizationId}/subscription": {
      "get": {
        "tags": [
          "OrganizationAnalytics"
        ],
        "summary": "The organization's subscription and seat position. Read-only.",
        "description": "Commercially sensitive: a learner must not be able to read what their\nemployer pays, so this needs string ApiScopes.ReadReports rather\nthan plain membership.\n\n**Requires scope:** `read:reports`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionDto"
                }
              }
            }
          }
        },
        "x-required-scope": "read:reports"
      }
    },
    "/api/v1/organizations/{organizationId}/courses": {
      "get": {
        "tags": [
          "OrganizationCourses"
        ],
        "summary": "Lists the organization's courses, published or not.",
        "description": "Distinct from `GET /api/v1/courses`, which shows a learner what they\nmay study. An author needs to see drafts, so this one is org-scoped and\nrequires membership rather than filtering on published state.\n\n**Requires scope:** `read:catalog`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CourseSummaryDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:catalog"
      },
      "post": {
        "tags": [
          "OrganizationCourses"
        ],
        "summary": "Creates a course.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCourseRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCourseRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCourseRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseSummaryDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/courses/{courseId}": {
      "put": {
        "tags": [
          "OrganizationCourses"
        ],
        "summary": "Replaces a course's metadata.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCourseRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCourseRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCourseRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseSummaryDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      },
      "delete": {
        "tags": [
          "OrganizationCourses"
        ],
        "summary": "Deletes a course. Refuses to take its sections with it unless asked.",
        "description": "`ICourseService.DeleteCourseAsync` is a hard cascade — it removes every\nsection and every lesson beneath them, with no soft-delete and nothing to\nundo it. That is defensible behind a web UI with a confirmation dialog. It\nis not defensible as the default for a call an automated client can make\nfrom a one-line instruction, so a course with content requires an explicit\n`cascade=true`. The refusal names what would have been destroyed.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cascade",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/courses/{courseId}/publish": {
      "post": {
        "tags": [
          "OrganizationCourses"
        ],
        "summary": "Publishes or unpublishes a course.",
        "description": "Separate from the full update so visibility can be changed without resending\nthe whole record — and so \"publish this\" cannot silently revert a field the\ncaller did not mean to touch.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublishRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/PublishRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/PublishRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseSummaryDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/departments": {
      "get": {
        "tags": [
          "OrganizationDepartments"
        ],
        "summary": "Lists the organization's departments.",
        "description": "**Requires scope:** `read:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OrgDepartmentDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:members"
      },
      "post": {
        "tags": [
          "OrganizationDepartments"
        ],
        "summary": "Creates a department.",
        "description": "**Requires scope:** `write:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDepartmentRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDepartmentRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDepartmentRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrgDepartmentDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:members"
      }
    },
    "/api/v1/organizations/{organizationId}/departments/{departmentId}": {
      "delete": {
        "tags": [
          "OrganizationDepartments"
        ],
        "summary": "Deletes a department. Members are not removed from the organization.",
        "description": "**Requires scope:** `write:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "departmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:members"
      }
    },
    "/api/v1/organizations/{organizationId}/departments/{departmentId}/members/{userId}": {
      "put": {
        "tags": [
          "OrganizationDepartments"
        ],
        "summary": "Adds a member to a department.",
        "description": "**Requires scope:** `write:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "departmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "write:members"
      },
      "delete": {
        "tags": [
          "OrganizationDepartments"
        ],
        "summary": "Removes a member from a department.",
        "description": "**Requires scope:** `write:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "departmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "write:members"
      }
    },
    "/api/v1/organizations/{organizationId}/lessons/{lessonId}/feedback": {
      "get": {
        "tags": [
          "OrganizationFeedback"
        ],
        "summary": "Every verdict on one lesson, newest first.",
        "description": "**Requires scope:** `read:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Rows to return. 1–500, default 200.",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LessonFeedbackEntryDto"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "read:content"
      }
    },
    "/api/v1/organizations/{organizationId}/content-feedback": {
      "get": {
        "tags": [
          "OrganizationFeedback"
        ],
        "summary": "Lessons that have been rated, hardest-to-follow first.",
        "description": "    The order of this list is the order of the work: the lesson at the top is\n    the one most learners said they did not understand.\n    minResponses keeps a single bad afternoon off the top.\nOne learner saying no is 100%, and a list led by 100%-of-one tells you\nnothing about what to rewrite.\n\n**Requires scope:** `read:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Inclusive start date (yyyy-MM-dd, Asia/Tokyo).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Inclusive end date (yyyy-MM-dd, Asia/Tokyo).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "minResponses",
            "in": "query",
            "description": "Minimum answers before a lesson is listed. Default 3.",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 3
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LessonFeedbackSummaryDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:content"
      }
    },
    "/api/v1/organizations/{organizationId}/courses/{courseId}/sections/{sectionId}/lessons": {
      "get": {
        "tags": [
          "OrganizationLessons"
        ],
        "summary": "Lists a section's lessons in order, drafts included.",
        "description": "**Requires scope:** `read:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sectionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LessonSummaryDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:content"
      },
      "post": {
        "tags": [
          "OrganizationLessons"
        ],
        "summary": "Creates a lesson at the end of a section.",
        "description": "Creates metadata only — the body arrives via the update, because\n`ILessonService` splits the two and a create that quietly accepted a\nbody would be doing something the domain does not model.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sectionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLessonRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLessonRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLessonRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonSummaryDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/courses/{courseId}/sections/{sectionId}/lessons/{lessonId}": {
      "put": {
        "tags": [
          "OrganizationLessons"
        ],
        "summary": "Replaces a lesson's content.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sectionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateLessonRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateLessonRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateLessonRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonSummaryDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      },
      "delete": {
        "tags": [
          "OrganizationLessons"
        ],
        "summary": "Deletes a lesson, along with its material links and quizzes.",
        "description": "No cascade flag here, unlike courses and sections. A lesson is the unit an\nauthor works in, and its quizzes and material links are parts of it rather\nthan separate things someone might not realise they owned — deleting a\nlesson and keeping its quiz would leave an orphan nothing can reach. The\nguard exists where a caller could destroy work they had not looked at.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sectionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/courses/{courseId}/sections/{sectionId}/lessons/{lessonId}/publish": {
      "post": {
        "tags": [
          "OrganizationLessons"
        ],
        "summary": "Publishes or unpublishes a lesson without resending its body.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sectionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublishRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/PublishRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/PublishRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonSummaryDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/materials": {
      "get": {
        "tags": [
          "OrganizationMaterials"
        ],
        "summary": "Lists the organization's materials.",
        "description": "**Requires scope:** `read:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MaterialDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:content"
      },
      "post": {
        "tags": [
          "OrganizationMaterials"
        ],
        "summary": "Creates a material.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMaterialRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMaterialRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMaterialRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/materials/{materialId}": {
      "get": {
        "tags": [
          "OrganizationMaterials"
        ],
        "summary": "Fetches one material.",
        "description": "**Requires scope:** `read:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "materialId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialDto"
                }
              }
            }
          }
        },
        "x-required-scope": "read:content"
      },
      "put": {
        "tags": [
          "OrganizationMaterials"
        ],
        "summary": "Updates a material's summary and content.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "materialId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMaterialRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMaterialRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMaterialRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MaterialDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      },
      "delete": {
        "tags": [
          "OrganizationMaterials"
        ],
        "summary": "Deletes a material, and its links to any lessons.",
        "description": "Reports how many lessons currently use it, because a material is shared by\ndesign: deleting one that three lessons reference silently strips content\nfrom all three, and the caller may only have had one in mind.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "materialId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cascade",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/lessons/{lessonId}/materials": {
      "put": {
        "tags": [
          "OrganizationMaterials"
        ],
        "summary": "Attaches a material to a lesson.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttachMaterialRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/AttachMaterialRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/AttachMaterialRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/lessons/{lessonId}/materials/{materialId}": {
      "delete": {
        "tags": [
          "OrganizationMaterials"
        ],
        "summary": "Detaches a material from a lesson, leaving the material itself.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "materialId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/members": {
      "get": {
        "tags": [
          "OrganizationMembers"
        ],
        "summary": "Lists the organization's members, including pending invitations.",
        "description": "**Requires scope:** `read:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OrgMemberDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:members"
      },
      "post": {
        "tags": [
          "OrganizationMembers"
        ],
        "summary": "Invites someone by email, creating the user if they are new.",
        "description": "Consumes a seat. Pending invitations count against the allowance, so a sync\ncannot queue up more people than the plan covers and settle the bill later.\n\n**Requires scope:** `write:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InviteMemberRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/InviteMemberRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/InviteMemberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrgMemberDto"
                }
              }
            }
          },
          "402": {
            "description": "Payment Required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:members"
      }
    },
    "/api/v1/organizations/{organizationId}/members/{userId}": {
      "get": {
        "tags": [
          "OrganizationMembers"
        ],
        "summary": "Gets one member.",
        "description": "**Requires scope:** `read:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrgMemberDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "read:members"
      },
      "delete": {
        "tags": [
          "OrganizationMembers"
        ],
        "summary": "Removes a member, freeing their seat.",
        "description": "**Requires scope:** `write:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:members"
      }
    },
    "/api/v1/organizations/{organizationId}/seats": {
      "get": {
        "tags": [
          "OrganizationMembers"
        ],
        "summary": "Current seat consumption for the organization's plan.",
        "description": "Worth reading before a bulk sync, so a run does not stop halfway.\n\n**Requires scope:** `read:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeatUsageDto"
                }
              }
            }
          }
        },
        "x-required-scope": "read:members"
      }
    },
    "/api/v1/organizations/{organizationId}/members/{userId}/roles": {
      "put": {
        "tags": [
          "OrganizationMembers"
        ],
        "summary": "Replaces a member's roles.",
        "description": "Send the complete set — every flag is applied as given, so an omitted role\nis cleared. Partial updates would make it impossible to revoke a role.\n\n**Requires scope:** `write:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMemberRolesRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMemberRolesRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMemberRolesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrgMemberDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:members"
      }
    },
    "/api/v1/organizations/{organizationId}/paths": {
      "get": {
        "tags": [
          "OrganizationPaths"
        ],
        "summary": "Lists the organization's paths.",
        "description": "**Requires scope:** `read:catalog`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PathDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:catalog"
      },
      "post": {
        "tags": [
          "OrganizationPaths"
        ],
        "summary": "Creates a path.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WritePathRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/WritePathRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/WritePathRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PathDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/paths/{pathId}": {
      "put": {
        "tags": [
          "OrganizationPaths"
        ],
        "summary": "Updates a path.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pathId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WritePathRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/WritePathRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/WritePathRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PathDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      },
      "delete": {
        "tags": [
          "OrganizationPaths"
        ],
        "summary": "Deletes a path. Refuses while it is assigned to anyone.",
        "description": "Unlike a course, a path is deleted out from under named people: the members\nit was assigned to lose the training they were told to do, and their\nprogress against it goes with it. The refusal names how many.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pathId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cascade",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/paths/{pathId}/courses": {
      "get": {
        "tags": [
          "OrganizationPaths"
        ],
        "summary": "Lists the courses in a path, in order.",
        "description": "**Requires scope:** `read:catalog`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pathId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PathCourseSummaryDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:catalog"
      },
      "put": {
        "tags": [
          "OrganizationPaths"
        ],
        "summary": "Sets the order of the courses in a path.",
        "description": "    Takes the full ordered list rather than a move operation. A path is short\n    enough to send whole, and it means the client states the order it wants\n    instead of the two of us having to agree on what \"move to position 3\" does\n    when someone else reordered it a second earlier.\n    Rejects a list that is not exactly the path's current courses, so a\nreorder cannot quietly add or drop one.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pathId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReorderCoursesRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/ReorderCoursesRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/ReorderCoursesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PathCourseSummaryDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/paths/{pathId}/courses/{courseId}": {
      "put": {
        "tags": [
          "OrganizationPaths"
        ],
        "summary": "Adds a course to a path.",
        "description": "Both ends are checked against the organization. Verifying only the path\nwould let an author pull another tenant's course into their own curriculum,\nwhich is a content leak dressed up as a structural edit.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pathId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "orderIndex",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PathCourseSummaryDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      },
      "delete": {
        "tags": [
          "OrganizationPaths"
        ],
        "summary": "Removes a course from a path. The course itself is untouched.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pathId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/members/{userId}/paths/{pathId}": {
      "put": {
        "tags": [
          "OrganizationReporting"
        ],
        "summary": "Assigns a learning path to one member.",
        "description": "**Requires scope:** `write:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pathId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "write:members"
      },
      "delete": {
        "tags": [
          "OrganizationReporting"
        ],
        "summary": "Removes a path assignment from a member.",
        "description": "**Requires scope:** `write:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pathId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "write:members"
      }
    },
    "/api/v1/organizations/{organizationId}/departments/{departmentId}/paths/{pathId}": {
      "put": {
        "tags": [
          "OrganizationReporting"
        ],
        "summary": "Assigns a learning path to a whole department.",
        "description": "Everyone in the department inherits it, including people added later.\n\n**Requires scope:** `write:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "departmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pathId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "write:members"
      },
      "delete": {
        "tags": [
          "OrganizationReporting"
        ],
        "summary": "Removes a path assignment from a department.",
        "description": "**Requires scope:** `write:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "departmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pathId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "write:members"
      }
    },
    "/api/v1/organizations/{organizationId}/progress": {
      "get": {
        "tags": [
          "OrganizationReporting"
        ],
        "summary": "Progress for every member, with an organization-wide rollup.",
        "description": "Computed in a fixed number of queries regardless of headcount, so it stays\nusable for a large organization. Requires `read:reports` — progress\nacross named people is more sensitive than the member list itself.\n\n**Requires scope:** `read:reports`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrgProgressReportDto"
                }
              }
            }
          }
        },
        "x-required-scope": "read:reports"
      }
    },
    "/api/v1/organizations/{organizationId}/courses/{courseId}/sections": {
      "get": {
        "tags": [
          "OrganizationSections"
        ],
        "summary": "Lists a course's sections in order.",
        "description": "**Requires scope:** `read:catalog`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SectionDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:catalog"
      },
      "post": {
        "tags": [
          "OrganizationSections"
        ],
        "summary": "Adds a section to the end of a course.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSectionRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSectionRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSectionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SectionDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/courses/{courseId}/sections/{sectionId}": {
      "put": {
        "tags": [
          "OrganizationSections"
        ],
        "summary": "Updates a section's title and summary.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sectionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSectionRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSectionRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSectionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SectionDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      },
      "delete": {
        "tags": [
          "OrganizationSections"
        ],
        "summary": "Deletes a section. Refuses to take its lessons with it unless asked.",
        "description": "Same reasoning as deleting a course: the destruction is unrecoverable, and\nan automated caller acting on a vague instruction should have to say it\nmeant the lessons too.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sectionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cascade",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/tests": {
      "get": {
        "tags": [
          "OrganizationTests"
        ],
        "summary": "Lists the organization's tests.",
        "description": "**Requires scope:** `read:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TestSummaryDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "read:content"
      },
      "post": {
        "tags": [
          "OrganizationTests"
        ],
        "summary": "Creates a test.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTestRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTestRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTestRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/tests/{testId}": {
      "get": {
        "tags": [
          "OrganizationTests"
        ],
        "summary": "Fetches a test with its answer key, for editing.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "testId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      },
      "put": {
        "tags": [
          "OrganizationTests"
        ],
        "summary": "Updates a test's metadata.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "testId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTestRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTestRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTestRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      },
      "delete": {
        "tags": [
          "OrganizationTests"
        ],
        "summary": "Deletes a test, its questions, and every result recorded against it.",
        "description": "The results are the reason this needs confirming. `DeleteTestAsync`\nremoves `TestResults` and `UserTestAnswers` too, so deleting a\nlive test erases the evidence that learners ever passed it — and those\nrecords are what certificates rest on.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "testId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cascade",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/tests/{testId}/questions": {
      "post": {
        "tags": [
          "OrganizationTests"
        ],
        "summary": "Adds a question with its options.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "testId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WriteQuestionRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/WriteQuestionRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/WriteQuestionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestQuestionDto"
                }
              }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/organizations/{organizationId}/tests/{testId}/questions/{questionId}": {
      "delete": {
        "tags": [
          "OrganizationTests"
        ],
        "summary": "Removes a question.",
        "description": "**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "testId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "questionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/projects": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "Lists projects for an organization, or a consultant's own portfolio.",
        "description": "One of `organizationId` or `consultantId` is required. A consultant may list only their own.",
        "parameters": [
          {
            "name": "organizationId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "consultantId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Creates a project in one of the caller's organizations.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectCreateDto"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectCreateDto"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectCreateDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/projects/{id}": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "Fetches one project.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "tags": [
          "Projects"
        ],
        "summary": "Updates a project.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectUpdateDto"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectUpdateDto"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectUpdateDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "tags": [
          "Projects"
        ],
        "summary": "Deletes a project.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/rag/index": {
      "post": {
        "tags": [
          "Rag"
        ],
        "summary": "Rebuilds the whole search index for this deployment.",
        "description": "Expensive and deployment-wide, so it is gated on `write:content` rather than a read scope.\n\n**Requires scope:** `write:content`",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-required-scope": "write:content"
      }
    },
    "/api/v1/search": {
      "get": {
        "tags": [
          "Search"
        ],
        "summary": "Searches the catalog.",
        "description": "Results are filtered to public content plus the caller's own organizations.\nThe index itself holds every tenant's material, so the filter here is the\nonly thing standing between a query and another customer's curriculum —\nit is applied to the returned documents rather than trusted to the query.\n\n**Requires scope:** `read:catalog`",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "top",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponseDto"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "read:catalog"
      }
    },
    "/stripe/webhook": {
      "post": {
        "tags": [
          "StripeWebhook"
        ],
        "summary": "Receives Stripe webhook events.",
        "description": "Called by Stripe, not by API clients. Authenticated by signature rather than a credential of ours.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/tasks": {
      "get": {
        "tags": [
          "Tasks"
        ],
        "summary": "Lists the tasks in a project.",
        "description": "`projectId` is required; tasks are authorized through their parent project.",
        "parameters": [
          {
            "name": "projectId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "Tasks"
        ],
        "summary": "Creates a task in a project.",
        "description": "Attribution comes from the credential, not the request body.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaskCreateRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/TaskCreateRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/TaskCreateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/tasks/{id}": {
      "get": {
        "tags": [
          "Tasks"
        ],
        "summary": "Fetches one task.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/tasks/{id}/toggle": {
      "put": {
        "tags": [
          "Tasks"
        ],
        "summary": "Flips a task between complete and incomplete.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/tests/{testId}": {
      "get": {
        "tags": [
          "Tests"
        ],
        "summary": "Fetches a test to sit, without its answer key.",
        "description": "**Requires scope:** `read:content`",
        "parameters": [
          {
            "name": "testId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestDetailDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "read:content"
      }
    },
    "/api/v1/tests/{testId}/submit": {
      "post": {
        "tags": [
          "Tests"
        ],
        "summary": "Submits answers to a test and returns the graded result.",
        "description": "    The whole attempt in one call — start, answer, submit — because a client\n    that has to orchestrate three requests can leave a half-finished attempt\n    behind when the network drops, and an abandoned attempt still counts as one.\n    Send an Idempotency-Key: a retry after a timeout would otherwise\ncount as a second sitting, and attempts gate certificates.\n\n**Requires scope:** `write:progress`",
        "parameters": [
          {
            "name": "testId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestSubmissionRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/TestSubmissionRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/TestSubmissionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestAttemptDto"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "x-required-scope": "write:progress"
      }
    },
    "/api/v1/users": {
      "get": {
        "tags": [
          "Users"
        ],
        "summary": "Lists users in an organization the caller belongs to.",
        "description": "**Requires scope:** `read:members`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-required-scope": "read:members"
      }
    },
    "/api/v1/users/{emailOrId}": {
      "get": {
        "tags": [
          "Users"
        ],
        "summary": "Looks up a single user, by id or email, within the caller's organizations.",
        "description": "Email lookup is kept because integrations reconcile against it, but it is\nthe shape worth being careful with: an unfiltered version is an oracle for\nwhether any given address has an account here, which is worth knowing to\nanyone building a phishing list. Restricting it to shared organizations\nmeans the caller learns nothing they could not already see.\n\n**Requires scope:** `read:members`",
        "parameters": [
          {
            "name": "emailOrId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-required-scope": "read:members"
      }
    },
    "/api/v1/organizations/{organizationId}/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Lists the organization's webhook endpoints.",
        "description": "Never returns signing secrets — those are shown once, at creation.\n\n**Requires scope:** `webhooks:manage`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WebhookSubscriptionDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "webhooks:manage"
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Registers an endpoint and returns its signing secret.",
        "description": "The secret is returned only here. Use it to verify the\n`X-Manabu-Signature` header on every delivery, and reject anything\nthat does not match — the URL is the only thing standing between your\nhandler and anyone who guesses it.\n\n**Requires scope:** `webhooks:manage`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreatedDto"
                }
              }
            }
          }
        },
        "x-required-scope": "webhooks:manage"
      }
    },
    "/api/v1/webhook-events": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "The event types available to subscribe to.",
        "description": "**Requires scope:** `webhooks:manage`",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "webhooks:manage"
      }
    },
    "/api/v1/organizations/{organizationId}/webhooks/{subscriptionId}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Deletes an endpoint and its delivery history.",
        "description": "**Requires scope:** `webhooks:manage`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "subscriptionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "webhooks:manage"
      }
    },
    "/api/v1/organizations/{organizationId}/webhooks/{subscriptionId}/test": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Queues a `ping` event, to check an endpoint is wired up.",
        "description": "Returns immediately; delivery happens in the background. Poll\n`/deliveries` to see the outcome.\n\n**Requires scope:** `webhooks:manage`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "subscriptionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "webhooks:manage"
      }
    },
    "/api/v1/organizations/{organizationId}/webhooks/{subscriptionId}/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Recent delivery attempts, newest first.",
        "description": "**Requires scope:** `webhooks:manage`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "subscriptionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WebhookDeliveryDto"
                  }
                }
              }
            }
          }
        },
        "x-required-scope": "webhooks:manage"
      }
    },
    "/api/v1/organizations/{organizationId}/webhooks/deliveries/{deliveryId}/replay": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Re-queues a delivery, for when an endpoint was down.",
        "description": "**Requires scope:** `webhooks:manage`",
        "parameters": [
          {
            "name": "organizationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "deliveryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique value (a UUID works well) that makes this call safe to retry: a repeat of the same request returns the original response with `Idempotency-Replayed: true` instead of executing again. Reusing a key for a different request returns 409. Keys are remembered for 24 hours.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": { }
            }
          }
        },
        "x-required-scope": "webhooks:manage"
      }
    }
  },
  "components": {
    "schemas": {
      "AccountLinkRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": [
              "null",
              "string"
            ]
          },
          "password": {
            "type": [
              "null",
              "string"
            ]
          },
          "displayName": {
            "type": [
              "null",
              "string"
            ],
            "description": "Optional display name, since a device account starts without one."
          }
        },
        "description": "Details for attaching a sign-in method to an anonymous account."
      },
      "AccountLinkResponse": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string",
            "description": "Unchanged. The point of linking is that this stays the same."
          },
          "email": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "description": "Confirmation that the account is now recoverable."
      },
      "AiDraftDto": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string"
          },
          "content": {
            "type": "string",
            "description": "The model's output. Markdown for a lesson body, JSON for an outline or\nquestion set — the shape each endpoint asked for, passed through unparsed\nso a client sees exactly what was produced."
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "A generated draft."
      },
      "AiLessonBodyRequest": {
        "required": [
          "lessonTitle"
        ],
        "type": "object",
        "properties": {
          "lessonTitle": {
            "maxLength": 300,
            "minLength": 1,
            "type": "string"
          },
          "courseTitle": {
            "type": [
              "null",
              "string"
            ]
          },
          "sectionTitle": {
            "type": [
              "null",
              "string"
            ]
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "AiOutlineRequest": {
        "required": [
          "topic"
        ],
        "type": "object",
        "properties": {
          "topic": {
            "maxLength": 500,
            "minLength": 3,
            "type": "string"
          },
          "audience": {
            "type": [
              "null",
              "string"
            ]
          },
          "lessonCount": {
            "maximum": 50,
            "minimum": 1,
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "AiQuizRequest": {
        "required": [
          "lessonTitle"
        ],
        "type": "object",
        "properties": {
          "lessonTitle": {
            "maxLength": 300,
            "minLength": 1,
            "type": "string"
          },
          "lessonBody": {
            "type": [
              "null",
              "string"
            ],
            "description": "The lesson body to draw questions from, when there is one."
          },
          "questionCount": {
            "maximum": 20,
            "minimum": 1,
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "ApiKeyCreatedDto": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "The full key. Not recoverable later; only its hash is stored."
          },
          "warning": {
            "type": "string"
          },
          "apiKey": {
            "$ref": "#/components/schemas/ApiKeySummaryDto"
          }
        },
        "description": "The response to creating a key — the one and only time the secret is returned."
      },
      "ApiKeySummaryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "maskedKey": {
            "type": "string",
            "description": "Prefix plus last 4, e.g. `mnb_live_…a1b2`. Not usable as a credential."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "expiresAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "revokedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "isActive": {
            "type": "boolean",
            "description": "False once revoked or past expiry."
          }
        },
        "description": "An API key as it can safely be shown. Carries no key material."
      },
      "AttachMaterialRequest": {
        "required": [
          "materialId"
        ],
        "type": "object",
        "properties": {
          "materialId": {
            "type": "string"
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "Attaches an existing material to a lesson at a given position."
      },
      "AuthResponse": {
        "type": "object",
        "properties": {
          "accessToken": {
            "type": "string",
            "description": "Short-lived bearer token. Send it as `Authorization: Bearer …`."
          },
          "expiresAt": {
            "type": "string",
            "description": "When string AuthResponse.AccessToken stops being accepted.",
            "format": "date-time"
          },
          "refreshToken": {
            "type": "string",
            "description": "Single-use token for obtaining the next access token. Replace your stored\ncopy with the one returned by each refresh — presenting an old one is\ntreated as theft and revokes the whole session."
          },
          "user": {
            "$ref": "#/components/schemas/UserSummary"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The scopes this token actually carries, after intersecting the user's roles\nwith what the client may request. Returned so an app can see what it got\nrather than discovering a missing scope through a 403 later."
          }
        }
      },
      "CertificateDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "organizationId": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "imageUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "requiredPathIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ClientRegistrationRequest": {
        "required": [
          "redirect_uris"
        ],
        "type": "object",
        "properties": {
          "client_name": {
            "type": [
              "null",
              "string"
            ]
          },
          "redirect_uris": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "scope": {
            "type": [
              "null",
              "string"
            ]
          },
          "grant_types": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "token_endpoint_auth_method": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CompletionPointDto": {
        "type": "object",
        "properties": {
          "year": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "month": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "lessonsCompleted": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "A point in the completion trend."
      },
      "CourseCompletionDto": {
        "type": "object",
        "properties": {
          "courseId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "isPublished": {
            "type": "boolean"
          },
          "totalLessons": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "learnersStarted": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "Members who have completed at least one lesson.",
            "format": "int32"
          },
          "learnersCompleted": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "Members who have completed every countable lesson.",
            "format": "int32"
          },
          "completionPercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "Completion for one course across the organization."
      },
      "CourseDetailDto": {
        "type": "object",
        "properties": {
          "sections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SectionDto"
            }
          },
          "id": {
            "type": "string"
          },
          "organizationId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "thumbnailUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "culture": {
            "type": "string"
          },
          "level": {
            "type": [
              "null",
              "string"
            ]
          },
          "estimatedMinutes": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isAi": {
            "type": "boolean",
            "description": "True when the course was drafted by the AI authoring tools."
          },
          "isPublic": {
            "type": "boolean",
            "description": "True when anyone may see this course, not only the owning organization."
          },
          "sectionCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "lessonCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        },
        "description": "A course with its sections and the lessons inside them."
      },
      "CourseProgressDetailDto": {
        "type": "object",
        "properties": {
          "lessons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LessonProgressDto"
            }
          },
          "courseId": {
            "type": "string"
          },
          "courseTitle": {
            "type": "string"
          },
          "culture": {
            "type": "string"
          },
          "totalLessons": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "Published lessons in the course — the denominator of int CourseProgressDto.PercentComplete.",
            "format": "int32"
          },
          "completedLessons": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "percentComplete": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "0–100. Zero when the course has no published lessons.",
            "format": "int32"
          },
          "isCompleted": {
            "type": "boolean",
            "description": "True when every published lesson has been completed."
          },
          "lastActivityAt": {
            "type": [
              "null",
              "string"
            ],
            "description": "Most recent view or completion in this course.",
            "format": "date-time"
          }
        },
        "description": "A course's progress broken down to the individual lessons."
      },
      "CourseProgressDto": {
        "type": "object",
        "properties": {
          "courseId": {
            "type": "string"
          },
          "courseTitle": {
            "type": "string"
          },
          "culture": {
            "type": "string"
          },
          "totalLessons": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "Published lessons in the course — the denominator of int CourseProgressDto.PercentComplete.",
            "format": "int32"
          },
          "completedLessons": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "percentComplete": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "0–100. Zero when the course has no published lessons.",
            "format": "int32"
          },
          "isCompleted": {
            "type": "boolean",
            "description": "True when every published lesson has been completed."
          },
          "lastActivityAt": {
            "type": [
              "null",
              "string"
            ],
            "description": "Most recent view or completion in this course.",
            "format": "date-time"
          }
        },
        "description": "How far the signed-in learner has got through one course."
      },
      "CourseSummaryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "organizationId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "thumbnailUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "culture": {
            "type": "string"
          },
          "level": {
            "type": [
              "null",
              "string"
            ]
          },
          "estimatedMinutes": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isAi": {
            "type": "boolean",
            "description": "True when the course was drafted by the AI authoring tools."
          },
          "isPublic": {
            "type": "boolean",
            "description": "True when anyone may see this course, not only the owning organization."
          },
          "sectionCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "lessonCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        },
        "description": "A course as it appears in a list."
      },
      "CreateApiKeyRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "A label so the key is recognisable later, e.g. \"Zapier integration\"."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Scopes to grant. Unrecognised values are ignored rather than rejected."
          },
          "expiresAt": {
            "type": [
              "null",
              "string"
            ],
            "description": "Optional expiry. Omit for a key that does not expire.",
            "format": "date-time"
          }
        }
      },
      "CreateCourseRequest": {
        "required": [
          "title"
        ],
        "type": "object",
        "properties": {
          "title": {
            "maxLength": 300,
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "thumbnailUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "isPublished": {
            "type": "boolean",
            "description": "Defaults to false so a create cannot publish by omission. Publishing is an\nexplicit act — it is what makes content visible to learners."
          },
          "culture": {
            "type": "string"
          },
          "estimatedMinutes": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "level": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "description": "Request bodies for the content-authoring endpoints."
      },
      "CreateDepartmentRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateLessonRequest": {
        "required": [
          "title"
        ],
        "type": "object",
        "properties": {
          "title": {
            "maxLength": 300,
            "minLength": 1,
            "type": "string"
          },
          "summary": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateMaterialRequest": {
        "required": [
          "contentRaw"
        ],
        "type": "object",
        "properties": {
          "contentRaw": {
            "type": "string"
          },
          "summary": {
            "type": [
              "null",
              "string"
            ]
          },
          "contentType": {
            "type": "string"
          }
        },
        "description": "Creates a material."
      },
      "CreateSectionRequest": {
        "required": [
          "title"
        ],
        "type": "object",
        "properties": {
          "title": {
            "maxLength": 300,
            "minLength": 1,
            "type": "string"
          },
          "summary": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateTestRequest": {
        "required": [
          "title"
        ],
        "type": "object",
        "properties": {
          "title": {
            "maxLength": 300,
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "pathId": {
            "type": [
              "null",
              "string"
            ]
          },
          "passingScorePercent": {
            "maximum": 100,
            "minimum": 0,
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isRequired": {
            "type": "boolean"
          },
          "culture": {
            "type": "string"
          }
        }
      },
      "CreateWebhookRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "An absolute https:// address."
          },
          "eventTypes": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            },
            "description": "Event types to receive. Omit or leave empty to receive everything —\nincluding event types added later."
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "DepartmentCompletionDto": {
        "type": "object",
        "properties": {
          "departmentId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "memberCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "lessonsCompleted": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "pathsAssigned": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "pathsCompleted": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "DeviceRegisterRequest": {
        "type": "object",
        "properties": {
          "clientId": {
            "type": [
              "null",
              "string"
            ],
            "description": "The registered client id of the app."
          },
          "deviceId": {
            "type": [
              "null",
              "string"
            ],
            "description": "A stable per-installation identifier. Not a secret and not trusted as one —\nit only says which installation is calling."
          },
          "platform": {
            "type": [
              "null",
              "string"
            ],
            "description": "Optional label for support, e.g. \"ios\" or \"android\"."
          }
        },
        "description": "First-launch registration."
      },
      "DeviceRegistrationResponse": {
        "type": "object",
        "properties": {
          "deviceSecret": {
            "type": [
              "null",
              "string"
            ],
            "description": "Shown once. Store it in the platform keystore immediately."
          },
          "secretWarning": {
            "type": "string"
          },
          "isNewAccount": {
            "type": "boolean",
            "description": "False when this installation already had an account."
          },
          "auth": {
            "$ref": "#/components/schemas/AuthResponse"
          }
        },
        "description": "Registration result: the one-time secret plus the usual tokens."
      },
      "DeviceTokenRequest": {
        "type": "object",
        "properties": {
          "clientId": {
            "type": [
              "null",
              "string"
            ]
          },
          "deviceId": {
            "type": [
              "null",
              "string"
            ]
          },
          "deviceSecret": {
            "type": [
              "null",
              "string"
            ],
            "description": "The secret returned at registration."
          }
        },
        "description": "Re-authentication using the stored device secret."
      },
      "GoalDto": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "The kind of goal: `high_school`, `university`, `company`, `certification`."
          },
          "source": {
            "type": "string",
            "description": "The catalogue the id came from, e.g. `wakaroute`."
          },
          "externalId": {
            "type": "string",
            "description": "The catalogue's identifier, e.g. `wk_d107320361045`."
          },
          "name": {
            "type": "string",
            "description": "The name as the catalogue gave it. Display only — never a key."
          },
          "rank": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "Position in the learner's own order. 0 is 第一志望.",
            "format": "int32"
          },
          "targetDate": {
            "type": [
              "null",
              "string"
            ],
            "description": "Exam or application date, `yyyy-MM-dd` in Asia/Tokyo. Null if not yet announced."
          }
        },
        "description": "One thing a learner is aiming at."
      },
      "GoalListDto": {
        "type": "object",
        "properties": {
          "goals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GoalDto"
            }
          },
          "bindingDeadline": {
            "type": [
              "null",
              "string"
            ],
            "description": "The earliest string? GoalDto.TargetDate across the whole list."
          },
          "daysRemaining": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "Whole days from today (Asia/Tokyo) to string? GoalListDto.BindingDeadline. Negative once it has passed.",
            "format": "int32"
          }
        },
        "description": "A learner's goals, in their order of preference."
      },
      "GoalListWriteDto": {
        "type": "object",
        "properties": {
          "type": {
            "type": [
              "null",
              "string"
            ],
            "description": "The kind being replaced. Omitted on `/me/target-schools`, which is\nalways `high_school`."
          },
          "goals": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/GoalWriteDto"
            },
            "description": "The full list, 第一志望 first. An empty array clears the learner's goals of\nthis kind, which is how a student says they have changed their mind."
          }
        },
        "description": "Replaces one kind of goal outright."
      },
      "GoalWriteDto": {
        "type": "object",
        "properties": {
          "source": {
            "maxLength": 40,
            "type": [
              "null",
              "string"
            ]
          },
          "externalId": {
            "maxLength": 100,
            "type": [
              "null",
              "string"
            ]
          },
          "name": {
            "maxLength": 200,
            "type": [
              "null",
              "string"
            ]
          },
          "targetDate": {
            "type": [
              "null",
              "string"
            ],
            "description": "Optional. `yyyy-MM-dd`."
          }
        },
        "description": "One entry in a replace-the-list write."
      },
      "IFormFile": {
        "type": "string",
        "format": "binary"
      },
      "InviteMemberRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "displayName": {
            "type": [
              "null",
              "string"
            ]
          },
          "department": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "description": "Invites someone by email, creating the user if they are new."
      },
      "JsonElement": { },
      "LessonCompletionDto": {
        "type": "object",
        "properties": {
          "lessonId": {
            "type": "string"
          },
          "courseId": {
            "type": "string"
          },
          "isCompleted": {
            "type": "boolean"
          },
          "course": {
            "description": "The course's progress after this change, so clients need no second call.",
            "$ref": "#/components/schemas/CourseProgressDto"
          }
        },
        "description": "Result of marking a lesson complete."
      },
      "LessonDetailDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "sectionId": {
            "type": "string"
          },
          "courseId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": [
              "null",
              "string"
            ]
          },
          "bodyHtml": {
            "type": [
              "null",
              "string"
            ],
            "description": "Rendered lesson body. The authoring source is not exposed."
          },
          "videoUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "slidesEmbedUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "culture": {
            "type": "string"
          },
          "materials": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MaterialSummaryDto"
            }
          },
          "quiz": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "The lesson's quiz, without the answer key. Null when there is none.",
                "$ref": "#/components/schemas/QuizDto"
              }
            ]
          }
        },
        "description": "A lesson with everything needed to render it."
      },
      "LessonFeedbackEntryDto": {
        "type": "object",
        "properties": {
          "feedbackId": {
            "type": "string"
          },
          "understood": {
            "type": "boolean"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "comment": {
            "type": [
              "null",
              "string"
            ],
            "description": "Null when none was written, or when it has been removed."
          },
          "commentRemoved": {
            "type": "boolean",
            "description": "True when a comment existed and no longer does — retention or account\nclosure. Distinct from never having written one."
          },
          "submittedAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "One learner's verdict, as content staff read it."
      },
      "LessonFeedbackReceiptDto": {
        "type": "object",
        "properties": {
          "feedbackId": {
            "type": "string"
          },
          "lessonId": {
            "type": "string"
          },
          "understood": {
            "type": "boolean"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "submittedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "What the learner gets back — enough to show \"thanks, saved\"."
      },
      "LessonFeedbackRequest": {
        "required": [
          "understood"
        ],
        "type": "object",
        "properties": {
          "understood": {
            "type": [
              "null",
              "boolean"
            ],
            "description": "True when the lesson made sense."
          },
          "reasons": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            },
            "description": "Why it did not: explanation, examples, questions, length, other."
          },
          "comment": {
            "maxLength": 1000,
            "minLength": 0,
            "type": [
              "null",
              "string"
            ],
            "description": "Optional free text, in the learner's own words."
          }
        },
        "description": "A learner's verdict on a lesson they have just read."
      },
      "LessonFeedbackSummaryDto": {
        "type": "object",
        "properties": {
          "lessonId": {
            "type": "string"
          },
          "lessonTitle": {
            "type": "string"
          },
          "sectionId": {
            "type": "string"
          },
          "courseId": {
            "type": "string"
          },
          "courseTitle": {
            "type": "string"
          },
          "responses": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "notUnderstoodCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "notUnderstoodPercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "The figure the list is sorted by.",
            "format": "int32"
          },
          "topReasons": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Reasons given, most common first."
          },
          "lastSubmittedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "How one lesson is landing, for the list that decides what to fix."
      },
      "LessonProgressDto": {
        "type": "object",
        "properties": {
          "lessonId": {
            "type": "string"
          },
          "sectionId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isViewed": {
            "type": "boolean"
          },
          "isCompleted": {
            "type": "boolean"
          },
          "viewedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "latestQuizScorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "The learner's most recent submitted quiz result on this lesson, when it has\na quiz they have sat. Null when it has no quiz, or they have not sat it.",
            "format": "int32"
          },
          "latestQuizPassed": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "latestQuizCompletedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        }
      },
      "LessonSummaryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "sectionId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": [
              "null",
              "string"
            ]
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "hasVideo": {
            "type": "boolean"
          },
          "hasSlides": {
            "type": "boolean"
          },
          "hasQuiz": {
            "type": "boolean"
          }
        },
        "description": "A lesson as it appears inside a course. Carries flags rather than the\ncontent itself, so a course page can be rendered in one request."
      },
      "LoginRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "password": {
            "type": "string"
          },
          "clientId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Which application is signing in. Determines the ceiling on the token's\nscopes: the result is what this app may request intersected with what the\nuser's roles allow."
          }
        }
      },
      "MaterialDetailDto": {
        "type": "object",
        "properties": {
          "organizationId": {
            "type": "string"
          },
          "contentType": {
            "type": "string"
          },
          "contentHtml": {
            "type": [
              "null",
              "string"
            ],
            "description": "Rendered material body. The authoring source is not exposed."
          },
          "fileUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "type": "string"
          },
          "summary": {
            "type": [
              "null",
              "string"
            ]
          },
          "fileName": {
            "type": [
              "null",
              "string"
            ]
          },
          "fileContentType": {
            "type": [
              "null",
              "string"
            ]
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "MaterialDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "organizationId": {
            "type": "string"
          },
          "summary": {
            "type": [
              "null",
              "string"
            ]
          },
          "contentHtml": {
            "type": "string"
          },
          "contentRaw": {
            "type": [
              "null",
              "string"
            ]
          },
          "contentType": {
            "type": "string"
          },
          "fileUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "fileName": {
            "type": [
              "null",
              "string"
            ]
          },
          "fileContentType": {
            "type": [
              "null",
              "string"
            ]
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "createdByUserId": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MaterialSummaryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "summary": {
            "type": [
              "null",
              "string"
            ]
          },
          "fileName": {
            "type": [
              "null",
              "string"
            ]
          },
          "fileContentType": {
            "type": [
              "null",
              "string"
            ]
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "A handout attached to a lesson. Materials carry no title of their own —\nstring? MaterialSummaryDto.Summary and string? MaterialSummaryDto.FileName are what identifies them."
      },
      "MemberProgressDto": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string"
          },
          "email": {
            "type": [
              "null",
              "string"
            ]
          },
          "displayName": {
            "type": [
              "null",
              "string"
            ]
          },
          "department": {
            "type": [
              "null",
              "string"
            ]
          },
          "progressPercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "0–100 across everything assigned to them.",
            "format": "int32"
          },
          "completedLessons": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "totalLessons": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "testScore": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "Average test score, 0–100.",
            "format": "int32"
          },
          "status": {
            "type": "string",
            "description": "One of `notstarted`, `learning`, `ontrack` or `delay`."
          },
          "lastActivityAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        },
        "description": "One member's progress across the organization's content."
      },
      "MyCertificateDto": {
        "type": "object",
        "properties": {
          "userCertificateId": {
            "type": "string",
            "description": "Id of the award itself, not of the certificate definition."
          },
          "certificateId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "imageUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "autoIssued": {
            "type": "boolean",
            "description": "True when issued automatically on completing the required paths."
          },
          "isExpired": {
            "type": "boolean",
            "description": "True when DateTime? MyCertificateDto.ExpiresAt is in the past."
          }
        },
        "description": "A certificate the signed-in learner has been awarded."
      },
      "MyPathDto": {
        "type": "object",
        "properties": {
          "pathId": {
            "type": "string"
          },
          "organizationId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "culture": {
            "type": "string"
          },
          "progressPercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isCompleted": {
            "type": "boolean"
          },
          "assignedAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "isAutoAssigned": {
            "type": "boolean",
            "description": "True when the path was assigned by a rule rather than by a person."
          }
        },
        "description": "A learning path assigned to the signed-in learner, with the progress the\nplatform has recorded against it."
      },
      "OrgDepartmentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "OrgMemberDto": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string"
          },
          "email": {
            "type": [
              "null",
              "string"
            ]
          },
          "displayName": {
            "type": [
              "null",
              "string"
            ]
          },
          "department": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "isOrgAdmin": {
            "type": "boolean"
          },
          "isCurriculumManager": {
            "type": "boolean"
          },
          "isLearner": {
            "type": "boolean"
          },
          "isHr": {
            "type": "boolean"
          },
          "isHrViewer": {
            "type": "boolean"
          },
          "isExecutive": {
            "type": "boolean"
          },
          "isDeveloper": {
            "type": "boolean",
            "description": "Can register applications and issue API keys for this organization."
          },
          "isPending": {
            "type": "boolean",
            "description": "True while an invitation is outstanding."
          },
          "joinedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "invitationSentAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        }
      },
      "OrgOverviewDto": {
        "type": "object",
        "properties": {
          "organizationId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "subscriptionPlan": {
            "type": "string"
          },
          "subscriptionStatus": {
            "type": "string"
          },
          "seatsUsed": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "seatsTotal": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "seatsAvailable": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "activeMembers": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "courseCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "publishedCourseCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "pathCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "activeLearners": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "Members with at least one lesson completed in the window.",
            "format": "int32"
          },
          "lessonsCompleted": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "An organization at a glance: headcount, seats, and how much learning is\nactually happening."
      },
      "OrgProgressReportDto": {
        "type": "object",
        "properties": {
          "organizationId": {
            "type": "string"
          },
          "memberCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "averageProgressPercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "description": "Mean of every member's progress, 0–100.",
            "format": "int32"
          },
          "membersByStatus": {
            "type": "object",
            "additionalProperties": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            },
            "description": "How many members are in each status. Keyed by the same vocabulary as\nstring MemberProgressDto.Status, and always sums to\nint OrgProgressReportDto.MemberCount."
          },
          "members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MemberProgressDto"
            }
          }
        },
        "description": "Organization-wide rollup, with the per-member rows behind it."
      },
      "PagedResponseOfCertificateDto": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CertificateDto"
            }
          },
          "page": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "pageSize": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "totalItems": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "totalPages": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "A page of results. Every list endpoint returns this shape so clients can\npage the same way everywhere."
      },
      "PagedResponseOfCourseSummaryDto": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CourseSummaryDto"
            }
          },
          "page": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "pageSize": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "totalItems": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "totalPages": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "A page of results. Every list endpoint returns this shape so clients can\npage the same way everywhere."
      },
      "PagedResponseOfPathSummaryDto": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PathSummaryDto"
            }
          },
          "page": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "pageSize": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "totalItems": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "totalPages": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "A page of results. Every list endpoint returns this shape so clients can\npage the same way everywhere."
      },
      "PathCourseSummaryDto": {
        "type": "object",
        "properties": {
          "courseId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isPublished": {
            "type": "boolean"
          },
          "addedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "A course as it sits inside a path."
      },
      "PathDetailDto": {
        "type": "object",
        "properties": {
          "courses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CourseSummaryDto"
            }
          },
          "id": {
            "type": "string"
          },
          "organizationId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "culture": {
            "type": "string"
          },
          "isPublic": {
            "type": "boolean"
          },
          "labels": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "courseCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "A path with its courses in the order learners should take them."
      },
      "PathDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "organizationId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "isPublic": {
            "type": "boolean"
          },
          "culture": {
            "type": "string"
          },
          "labels": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "courseCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "memberCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PathSummaryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "organizationId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "culture": {
            "type": "string"
          },
          "isPublic": {
            "type": "boolean"
          },
          "labels": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "courseCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ]
          },
          "instance": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ProjectCreateDto": {
        "type": "object",
        "properties": {
          "organizationId": {
            "type": "string"
          },
          "organizationDepartmentId": {
            "type": [
              "null",
              "string"
            ]
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "goal": {
            "type": [
              "null",
              "string"
            ]
          },
          "phase": {
            "type": [
              "null",
              "string"
            ]
          },
          "progress": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "labels": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "createdBy": {
            "type": "string"
          },
          "consultantFee": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "estimatedHoursPerMember": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "estimatedValue": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "ProjectUpdateDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "goal": {
            "type": [
              "null",
              "string"
            ]
          },
          "phase": {
            "type": [
              "null",
              "string"
            ]
          },
          "progress": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "labels": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "organizationDepartmentId": {
            "type": [
              "null",
              "string"
            ]
          },
          "consultantFee": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "estimatedHoursPerMember": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "estimatedValue": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "PublishRequest": {
        "type": "object",
        "properties": {
          "isPublished": {
            "type": "boolean"
          }
        },
        "description": "Sets whether a course or lesson is visible to learners."
      },
      "QuizAnswerInput": {
        "type": "object",
        "properties": {
          "questionId": {
            "type": "string"
          },
          "optionId": {
            "type": [
              "null",
              "string"
            ]
          },
          "textAnswer": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "description": "One answer. Supply string? QuizAnswerInput.OptionId for choice questions and\nstring? QuizAnswerInput.TextAnswer for free-text ones."
      },
      "QuizAttemptDto": {
        "type": "object",
        "properties": {
          "attemptId": {
            "type": "string"
          },
          "quizId": {
            "type": "string"
          },
          "lessonId": {
            "type": "string"
          },
          "lessonTitle": {
            "type": "string"
          },
          "courseId": {
            "type": "string"
          },
          "courseTitle": {
            "type": "string"
          },
          "scorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "passingScorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isPassed": {
            "type": "boolean"
          },
          "startedAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "One past quiz attempt, as it appears in the learner's own history."
      },
      "QuizDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "instructions": {
            "type": [
              "null",
              "string"
            ]
          },
          "passingScorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isRequired": {
            "type": "boolean"
          },
          "questions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/QuizQuestionDto"
            }
          }
        },
        "description": "A quiz as a learner may see it before answering."
      },
      "QuizOptionDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "An answer option. Whether it is correct is not disclosed."
      },
      "QuizQuestionDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "questionText": {
            "type": "string"
          },
          "imageUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "questionType": {
            "type": "string"
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/QuizOptionDto"
            }
          }
        }
      },
      "QuizResultDto": {
        "type": "object",
        "properties": {
          "attemptId": {
            "type": "string"
          },
          "quizId": {
            "type": "string"
          },
          "scorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "passingScorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isPassed": {
            "type": "boolean"
          },
          "completedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        },
        "description": "The graded outcome of a quiz attempt. Scoring happens server-side; the\nanswer key is never sent to the client."
      },
      "QuizSubmissionRequest": {
        "type": "object",
        "properties": {
          "answers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/QuizAnswerInput"
            }
          }
        },
        "description": "A learner's answers to one quiz, submitted in a single request."
      },
      "RecordStudySessionDto": {
        "type": "object",
        "properties": {
          "clientSessionId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Strongly recommended. Stable per session on the device."
          },
          "courseId": {
            "type": [
              "null",
              "string"
            ]
          },
          "lessonId": {
            "type": [
              "null",
              "string"
            ]
          },
          "subject": {
            "type": [
              "null",
              "string"
            ]
          },
          "kind": {
            "type": [
              "null",
              "string"
            ]
          },
          "startedAt": {
            "type": [
              "null",
              "string"
            ],
            "description": "Required. When study began, with an offset.",
            "format": "date-time"
          },
          "endedAt": {
            "type": [
              "null",
              "string"
            ],
            "description": "When study ended. Give this or int? RecordStudySessionDto.DurationSeconds.",
            "format": "date-time"
          },
          "durationSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "How long it ran. Give this or DateTimeOffset? RecordStudySessionDto.EndedAt.",
            "format": "int32"
          }
        },
        "description": "Records a session that already finished — an offline replay."
      },
      "RefreshRequest": {
        "type": "object",
        "properties": {
          "refreshToken": {
            "type": "string"
          }
        },
        "description": "Carries a refresh token, for renewing or ending a session."
      },
      "ReorderCoursesRequest": {
        "required": [
          "courseIds"
        ],
        "type": "object",
        "properties": {
          "courseIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "description": "The full ordered list of course ids in a path."
      },
      "SearchHitDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "organizationId": {
            "type": [
              "null",
              "string"
            ]
          },
          "isPublic": {
            "type": "boolean",
            "description": "True when the item is visible outside its owning organization."
          }
        },
        "description": "One search hit, across courses and paths."
      },
      "SearchResponseDto": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string"
          },
          "hits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchHitDto"
            }
          }
        }
      },
      "SeatUsageDto": {
        "type": "object",
        "properties": {
          "used": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "total": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "available": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        },
        "description": "Seat consumption for the organization's plan."
      },
      "SectionDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": [
              "null",
              "string"
            ]
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "lessons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LessonSummaryDto"
            }
          }
        }
      },
      "SessionDto": {
        "type": "object",
        "properties": {
          "sessionId": {
            "type": "string",
            "description": "Stable id for the session (its token family). Not a credential."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "userAgent": {
            "type": [
              "null",
              "string"
            ]
          },
          "ipAddress": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "description": "One active session, as shown to the user who owns it."
      },
      "StartStudySessionDto": {
        "type": "object",
        "properties": {
          "courseId": {
            "type": [
              "null",
              "string"
            ]
          },
          "lessonId": {
            "type": [
              "null",
              "string"
            ]
          },
          "subject": {
            "type": [
              "null",
              "string"
            ]
          },
          "kind": {
            "type": [
              "null",
              "string"
            ],
            "description": "Optional. Defaults to `learn`."
          }
        },
        "description": "Opens the clock."
      },
      "StudySessionDto": {
        "type": "object",
        "properties": {
          "sessionId": {
            "type": "string"
          },
          "clientSessionId": {
            "type": [
              "null",
              "string"
            ],
            "description": "The client's own id, when it supplied one."
          },
          "courseId": {
            "type": [
              "null",
              "string"
            ]
          },
          "lessonId": {
            "type": [
              "null",
              "string"
            ]
          },
          "subject": {
            "type": [
              "null",
              "string"
            ],
            "description": "Subject label for the calendar breakdown, e.g. 数学."
          },
          "kind": {
            "type": "string",
            "description": "`learn`, `practice`, `review` or `check`."
          },
          "startedAt": {
            "type": "string",
            "format": "date-time"
          },
          "endedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "studyDate": {
            "type": "string",
            "description": "The Asia/Tokyo date this counts towards. A session spanning midnight belongs wholly to its start date."
          },
          "durationSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "Null while running. Zero for an abandoned session.",
            "format": "int32"
          },
          "status": {
            "type": "string",
            "description": "`running`, `completed` or `abandoned`."
          },
          "recordedBy": {
            "type": "string",
            "description": "`timer` if this service stamped both ends, `reported` if the client did."
          }
        },
        "description": "One recorded stretch of studying."
      },
      "StudySessionListDto": {
        "type": "object",
        "properties": {
          "sessions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StudySessionDto"
            }
          },
          "running": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "The session currently running, if any. Not included in List&lt;StudySessionDto&gt; StudySessionListDto.Sessions totals.",
                "$ref": "#/components/schemas/StudySessionDto"
              }
            ]
          }
        },
        "description": "The activity log over a range."
      },
      "StudyStreakDto": {
        "type": "object",
        "properties": {
          "currentDays": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "longestDays": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "lastStudyDate": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "description": "How consistently the learner has been studying."
      },
      "StudySummaryDto": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "The day, or the first of the month for monthly granularity."
          },
          "totalSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "sessionCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "bySubject": {
            "type": "object",
            "additionalProperties": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            },
            "description": "Seconds per subject. Empty when no session named one."
          }
        },
        "description": "One bucket on the calendar."
      },
      "SubscriptionDto": {
        "type": "object",
        "properties": {
          "organizationId": {
            "type": "string"
          },
          "plan": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "seatsIncluded": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "paidSeats": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "bonusSeats": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "seatsUsed": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "seatsAvailable": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isPro": {
            "type": "boolean"
          },
          "hasBillingAccount": {
            "type": "boolean",
            "description": "Whether the organization is linked to a payment provider. The customer\nreference itself is deliberately not returned — it is a handle into our\nbilling account, not something a client needs."
          }
        },
        "description": "Subscription state. Read-only by decision (t-d1bea53): nothing here moves\nmoney, and there is no write counterpart anywhere in the API."
      },
      "TaskCreateRequest": {
        "type": "object",
        "properties": {
          "projectId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "createdBy": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "TestAnswerInput": {
        "required": [
          "questionId",
          "optionId"
        ],
        "type": "object",
        "properties": {
          "questionId": {
            "type": "string"
          },
          "optionId": {
            "type": "string"
          }
        }
      },
      "TestAttemptDto": {
        "type": "object",
        "properties": {
          "resultId": {
            "type": "string"
          },
          "testId": {
            "type": "string"
          },
          "scorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "passingScorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isPassed": {
            "type": "boolean"
          },
          "correctCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "totalQuestions": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "completedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "timeLimitSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "The test's limit in seconds, or null when untimed.",
            "format": "int32"
          },
          "elapsedSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "What the client reported for this sitting, or null.",
            "format": "int32"
          },
          "isWithinTimeLimit": {
            "type": [
              "null",
              "boolean"
            ],
            "description": "Whether the sitting finished inside the limit."
          }
        },
        "description": "The graded outcome of a test attempt."
      },
      "TestAttemptHistoryDto": {
        "type": "object",
        "properties": {
          "resultId": {
            "type": "string"
          },
          "testId": {
            "type": "string"
          },
          "testTitle": {
            "type": "string"
          },
          "pathId": {
            "type": [
              "null",
              "string"
            ]
          },
          "scorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "passingScorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isPassed": {
            "type": "boolean"
          },
          "correctCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "totalQuestions": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "completedAt": {
            "type": "string",
            "format": "date-time"
          },
          "timeLimitSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "The limit in force, or null when the test is untimed.",
            "format": "int32"
          },
          "elapsedSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "What the client reported for this sitting, or null.",
            "format": "int32"
          },
          "isWithinTimeLimit": {
            "type": [
              "null",
              "boolean"
            ],
            "description": "Null when unanswerable — no limit, or no elapsed time reported. Same rule\nas the submission response: never true by default."
          }
        },
        "description": "One past test sitting, as it appears in the learner's own history."
      },
      "TestDetailDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "organizationId": {
            "type": "string"
          },
          "pathId": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "passingScorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isRequired": {
            "type": "boolean"
          },
          "culture": {
            "type": "string"
          },
          "timeLimitSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "Seconds allowed, or null when untimed. Sent so the client can run a\ncountdown — the server does not stop a late submission, it records it.",
            "format": "int32"
          },
          "questions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TestQuestionViewDto"
            }
          }
        },
        "description": "A test with its questions, as delivered to someone about to sit it."
      },
      "TestDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "organizationId": {
            "type": [
              "null",
              "string"
            ]
          },
          "pathId": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "passingScorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isRequired": {
            "type": "boolean"
          },
          "timeLimitSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "culture": {
            "type": "string"
          },
          "questions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TestQuestionDto"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TestOptionDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "questionId": {
            "type": "string"
          },
          "optionText": {
            "type": "string"
          },
          "imageUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "isCorrect": {
            "type": "boolean"
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "TestOptionViewDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "optionText": {
            "type": "string"
          },
          "imageUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "TestQuestionDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "testId": {
            "type": "string"
          },
          "questionText": {
            "type": "string"
          },
          "explanation": {
            "type": [
              "null",
              "string"
            ]
          },
          "questionType": {
            "type": "string"
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TestOptionDto"
            }
          }
        }
      },
      "TestQuestionViewDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "questionText": {
            "type": "string"
          },
          "questionType": {
            "type": "string"
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TestOptionViewDto"
            },
            "description": "Deliberately absent while sitting: the explanation usually gives the answer\naway. It belongs with the result, not the question paper."
          }
        }
      },
      "TestSubmissionRequest": {
        "type": "object",
        "properties": {
          "answers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TestAnswerInput"
            }
          },
          "elapsedSeconds": {
            "maximum": 86400,
            "minimum": 0,
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "How long the learner took, in seconds. Optional.",
            "format": "int32"
          }
        },
        "description": "Answers to a test, submitted in one call and graded server-side."
      },
      "TestSummaryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "organizationId": {
            "type": "string"
          },
          "pathId": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "passingScorePercent": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isRequired": {
            "type": "boolean"
          },
          "culture": {
            "type": "string"
          },
          "questionCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "timeLimitSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "Seconds allowed, or null when the test is untimed.",
            "format": "int32"
          },
          "latestResult": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "The caller's most recent result, when they have sat it.",
                "$ref": "#/components/schemas/TestAttemptDto"
              }
            ]
          }
        },
        "description": "A test as it appears in a list."
      },
      "UpdateCourseRequest": {
        "required": [
          "title"
        ],
        "type": "object",
        "properties": {
          "title": {
            "maxLength": 300,
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "thumbnailUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "isPublished": {
            "type": "boolean"
          },
          "culture": {
            "type": "string"
          },
          "estimatedMinutes": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "level": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "UpdateLessonRequest": {
        "required": [
          "title",
          "bodyRaw"
        ],
        "type": "object",
        "properties": {
          "title": {
            "maxLength": 300,
            "minLength": 1,
            "type": "string"
          },
          "summary": {
            "type": [
              "null",
              "string"
            ]
          },
          "bodyRaw": {
            "type": "string",
            "description": "The lesson body. Required, because this replaces it."
          },
          "isPublished": {
            "type": "boolean"
          },
          "videoUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "slidesEmbedUrl": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "description": "Updates a lesson's full content."
      },
      "UpdateMaterialRequest": {
        "required": [
          "contentRaw"
        ],
        "type": "object",
        "properties": {
          "contentRaw": {
            "type": "string"
          },
          "summary": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "UpdateMemberRolesRequest": {
        "type": "object",
        "properties": {
          "isOrgAdmin": {
            "type": "boolean"
          },
          "isCurriculumManager": {
            "type": "boolean"
          },
          "isLearner": {
            "type": "boolean"
          },
          "isHr": {
            "type": "boolean"
          },
          "isHrViewer": {
            "type": "boolean"
          },
          "isExecutive": {
            "type": "boolean"
          },
          "isDeveloper": {
            "type": "boolean",
            "description": "Can register applications and issue API keys for this organization."
          }
        },
        "description": "Replaces a member's roles. Every flag is applied as given, so send the full\nset — omitting one clears it rather than leaving it untouched."
      },
      "UpdateSectionRequest": {
        "required": [
          "title"
        ],
        "type": "object",
        "properties": {
          "title": {
            "maxLength": 300,
            "minLength": 1,
            "type": "string"
          },
          "summary": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "UpdateTestRequest": {
        "required": [
          "title"
        ],
        "type": "object",
        "properties": {
          "title": {
            "maxLength": 300,
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "pathId": {
            "type": [
              "null",
              "string"
            ]
          },
          "passingScorePercent": {
            "maximum": 100,
            "minimum": 0,
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "isRequired": {
            "type": "boolean"
          },
          "culture": {
            "type": "string"
          }
        }
      },
      "UserSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          }
        }
      },
      "WebhookCreatedDto": {
        "type": "object",
        "properties": {
          "secret": {
            "type": "string",
            "description": "Use this to verify the `X-Manabu-Signature` header."
          },
          "warning": {
            "type": "string"
          },
          "webhook": {
            "$ref": "#/components/schemas/WebhookSubscriptionDto"
          }
        },
        "description": "Creation response — the only place the signing secret appears."
      },
      "WebhookDeliveryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "eventType": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "`pending`, `succeeded` or `failed`."
          },
          "attemptCount": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "lastStatusCode": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "description": "HTTP status of the last attempt; null if the request never completed.",
            "format": "int32"
          },
          "lastError": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastAttemptAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "deliveredAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "nextAttemptAt": {
            "type": [
              "null",
              "string"
            ],
            "description": "When the next retry is due. Null unless still pending.",
            "format": "date-time"
          }
        },
        "description": "One delivery attempt chain, for debugging an endpoint."
      },
      "WebhookSubscriptionDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "eventTypes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Events delivered to this endpoint. Always the resolved list — a\nsubscription created with no filter reports every event type rather than\nan empty array."
          },
          "isActive": {
            "type": "boolean"
          },
          "disabledAt": {
            "type": [
              "null",
              "string"
            ],
            "description": "Set when we stopped delivering, as opposed to you turning it off.",
            "format": "date-time"
          },
          "disabledReason": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "A registered webhook endpoint. Carries no signing secret."
      },
      "WriteOptionRequest": {
        "required": [
          "optionText"
        ],
        "type": "object",
        "properties": {
          "optionText": {
            "maxLength": 1000,
            "minLength": 1,
            "type": "string"
          },
          "imageUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "isCorrect": {
            "type": "boolean"
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "WritePathRequest": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "name": {
            "maxLength": 300,
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "isPublic": {
            "type": "boolean",
            "description": "Whether anyone may see this path, not only the owning organization.\nDefaults to false so a create cannot publish by omission."
          },
          "labels": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            },
            "description": "Labels for grouping, e.g. 数学."
          },
          "culture": {
            "type": "string"
          }
        }
      },
      "WriteQuestionRequest": {
        "required": [
          "questionText",
          "options"
        ],
        "type": "object",
        "properties": {
          "questionText": {
            "maxLength": 2000,
            "minLength": 1,
            "type": "string"
          },
          "explanation": {
            "type": [
              "null",
              "string"
            ]
          },
          "questionType": {
            "type": "string"
          },
          "orderIndex": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "options": {
            "minItems": 2,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WriteOptionRequest"
            }
          }
        },
        "description": "A question and its options, written together."
      }
    },
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "description": "A learner's token from POST /api/auth/login.",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      },
      "ApiKeyAuth": {
        "type": "apiKey",
        "description": "An organization API key (mnb_live_…). May also be sent as `Authorization: Bearer mnb_live_…` — the prefix distinguishes it from a JWT.",
        "name": "X-Api-Key",
        "in": "header"
      }
    }
  },
  "security": [
    { },
    { }
  ],
  "tags": [
    {
      "name": "AccountLink"
    },
    {
      "name": "Ai"
    },
    {
      "name": "ApiKeys"
    },
    {
      "name": "Auth"
    },
    {
      "name": "Courses"
    },
    {
      "name": "Lessons"
    },
    {
      "name": "Materials"
    },
    {
      "name": "Paths"
    },
    {
      "name": "Certificates"
    },
    {
      "name": "Devices"
    },
    {
      "name": "Mcp"
    },
    {
      "name": "MeAccount"
    },
    {
      "name": "Me"
    },
    {
      "name": "LessonProgress"
    },
    {
      "name": "MeGoals"
    },
    {
      "name": "MeStudy"
    },
    {
      "name": "OAuth"
    },
    {
      "name": "OAuthDiscovery"
    },
    {
      "name": "OAuthRegistration"
    },
    {
      "name": "OrganizationAi"
    },
    {
      "name": "OrganizationAnalytics"
    },
    {
      "name": "OrganizationCourses"
    },
    {
      "name": "OrganizationDepartments"
    },
    {
      "name": "OrganizationFeedback"
    },
    {
      "name": "OrganizationLessons"
    },
    {
      "name": "OrganizationMaterials"
    },
    {
      "name": "OrganizationMembers"
    },
    {
      "name": "OrganizationPaths"
    },
    {
      "name": "OrganizationReporting"
    },
    {
      "name": "OrganizationSections"
    },
    {
      "name": "OrganizationTests"
    },
    {
      "name": "Projects"
    },
    {
      "name": "Rag"
    },
    {
      "name": "Search"
    },
    {
      "name": "StripeWebhook"
    },
    {
      "name": "Tasks"
    },
    {
      "name": "Tests"
    },
    {
      "name": "Users"
    },
    {
      "name": "Webhooks"
    }
  ]
}