{"openapi": "3.1.0", "info": {"title": "PedsHub", "description": "The API behind PedsHub: a question bank sat as timed or study sessions, a library of articles and cards written against it, and the analysis of what a learner actually knows.\n\nEvery route lives under `/api/v1`. `/api/...` reaches the same route and always will \u2014 it is the address the web app was written against \u2014 but a client written today should say the version, so that the day `/api/v2` exists it keeps the behaviour it was built on.\n\nErrors carry an `error` object: `code` is a stable word to switch on, `message` is one line for a person, and `fields` says which inputs were wrong when that is the problem.", "version": "3.0.0"}, "paths": {"/uploads/{path}": {"get": {"summary": "Read Upload", "description": "An upload, optionally at one of two smaller widths.\n\n`?w=256` and `?w=640` are the only sizes there are, and anything else is\nrefused rather than honoured \u2014 an endpoint that resizes to whatever the\nquery string asks for is a CPU sink anybody can point at. The authorisation\nbelow is unchanged and runs first: a thumbnail of a file you may not read\nis a file you may not read.\n\nA derivative may be kept by the browser that fetched it; an original may\nnot. `private` in both cases \u2014 never a shared cache, because a shared cache\nin front of access-controlled images is how one learner is served another's\nprivate figure. What that leaves is the requester's own browser, which has\nalready been allowed to see the bytes, and which was re-fetching every\nthumbnail on every page for no reason.\n\nA derivative is safe to keep because it cannot change: `thumbs/256/<key>`\nis made once from an immutable original and never rewritten. Losing access\nto an image does not evict it from that one browser's cache for a week,\nwhich is the honest cost, and a small one for a picture that browser was\nentitled to draw yesterday.", "operationId": "read_upload_uploads__path__get", "parameters": [{"name": "path", "in": "path", "required": true, "schema": {"type": "string", "title": "Path"}}, {"name": "attempt_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Attempt Id"}}, {"name": "w", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "W"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/auth/login": {"post": {"tags": ["auth"], "summary": "Login", "description": "Closed. Every account signs in through One Sign In.\n\nThis was the last password door, kept for administrators as the way back\nin if the provider was down. The way back in is the provider's own\nrecovery (see ADMIN.md), and a second credential nobody rotates is a hole,\nnot a spare key. The address stays so an old client is told plainly.", "operationId": "login_api_v1_auth_login_post", "responses": {"410": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/v1/auth/refresh": {"post": {"tags": ["auth"], "summary": "Refresh", "description": "Trade a refresh token for a new pair.\n\nThe old one is spent by this call. Presenting a spent token ends the whole\nsession it belongs to: either it was copied or a client is replaying, and\nfrom here those look the same.", "operationId": "refresh_api_v1_auth_refresh_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RefreshRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Token"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/auth/logout": {"post": {"tags": ["auth"], "summary": "Logout", "description": "End this session, or every session.\n\nAn access token already issued is a signature and cannot be recalled; it\ndies of old age within the day. What this ends is the ability to get\nanother one, which is what a lost phone actually needs.", "operationId": "logout_api_v1_auth_logout_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LogoutRequest"}}}, "required": true}, "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/auth/sessions": {"get": {"tags": ["auth"], "summary": "List Sessions", "description": "Where this account is signed in, so a person can see and end them.", "operationId": "list_sessions_api_v1_auth_sessions_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/auth/sessions/{family}": {"delete": {"tags": ["auth"], "summary": "End Session", "operationId": "end_session_api_v1_auth_sessions__family__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "family", "in": "path", "required": true, "schema": {"type": "string", "title": "Family"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/auth/me/settings": {"get": {"tags": ["auth"], "summary": "Get User Settings", "description": "Get user settings. Most settings live in Redis;\nreminders_disabled is canonical in Postgres and overrides Redis.", "operationId": "get_user_settings_api_v1_auth_me_settings_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "put": {"tags": ["auth"], "summary": "Save User Settings", "description": "Save user settings. reminders_disabled is persisted to Postgres;\nother keys go to Redis.", "operationId": "save_user_settings_api_v1_auth_me_settings_put", "requestBody": {"content": {"application/json": {"schema": {"additionalProperties": true, "type": "object", "title": "Settings Data"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/auth/me": {"get": {"tags": ["auth"], "summary": "Get Me", "operationId": "get_me_api_v1_auth_me_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserResponse"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "put": {"tags": ["auth"], "summary": "Update Me", "description": "Your name. Your sign-in belongs to the provider.\n\nThis used to set a password as well. There is nowhere for one to be used\nthat the provider does not own, so writing one here would store a\ncredential nothing checks \u2014 and the settings page that offered it invited\nsomebody to secure their account by a route that secures nothing.", "operationId": "update_me_api_v1_auth_me_put", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserUpdateMe"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/auth/sso/exchange": {"post": {"tags": ["auth"], "summary": "Sso Exchange", "description": "Trade a one-time code for the token it stands for.\n\nOnce. The key is deleted as it is read, so a code replayed from a log, a\nhistory entry or a Referer header buys nothing. A client that asks for a\nrefresh token gets one too \u2014 an app has nowhere safe to keep a credential\nand no person in front of it to send back through the provider nightly.", "operationId": "sso_exchange_api_v1_auth_sso_exchange_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SsoExchangeRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Token"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/auth/sso/config": {"get": {"tags": ["auth"], "summary": "Sso Config", "description": "Public endpoint \u2014 tells frontend whether SSO is available and login mode.", "operationId": "sso_config_api_v1_auth_sso_config_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/v1/auth/sso/login": {"get": {"tags": ["auth"], "summary": "Sso Login", "description": "Redirect user to the OIDC provider for login.\n\n`async`, and the redirect awaited. Authlib's Starlette client is the async\none \u2014 `authorize_redirect` hands back a coroutine \u2014 so a sync endpoint\nreturned that coroutine to FastAPI, which tried to serialise it as a\nresponse body and answered 500: \"'coroutine' object is not iterable\". The\nbutton had never worked; nothing found it because nothing had SSO\nconfigured to click it with.", "operationId": "sso_login_api_v1_auth_sso_login_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/v1/auth/sso/logout": {"get": {"tags": ["auth"], "summary": "Sso Logout", "description": "End the session at the provider, not only here.\n\nSigning out used to mean \"this app forgets you\": the token went, and the\nprovider's session did not \u2014 so pressing Sign in put you straight back in\nwith no code. On a shared machine that is the wrong default and the one\nnobody expects.\n\nSo the browser is sent to the provider's end-session endpoint, which ends\nthe session behind both apps. That is the cost and it is deliberate: there\nis one session, and \"sign out\" should mean the same word in both places.\n\nNo id_token_hint: neither app keeps the id token, the endpoint is per\napplication, and the redirect is checked against the provider's own list.\nIf the provider will not take our redirect it shows its own \"you have\nlogged out\" page, which still ends the session \u2014 a worse landing, not a\nfailure.", "operationId": "sso_logout_api_v1_auth_sso_logout_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/v1/auth/sso/callback": {"get": {"tags": ["auth"], "summary": "Sso Callback", "description": "Handle OIDC provider callback \u2014 create or login user.", "operationId": "sso_callback_api_v1_auth_sso_callback_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/v1/public/stats": {"get": {"tags": ["public"], "summary": "Public Stats", "description": "What the landing page says there is, taken from what there is.\n\nA number written into the markup is a number that goes stale the week\nafter it is written, and nobody notices because nothing breaks.", "operationId": "public_stats_api_v1_public_stats_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/v1/articles/": {"get": {"tags": ["articles"], "summary": "List Articles", "description": "The published library. Drafts are Editorial's business, not Reading's.\n\nAn educator used to see their drafts mixed into the shelf here, marked\nwith a tag \u2014 which made Reading and Editorial two views of the same list\nand left an admin unsure which one he was looking at. Reading is what a\nlearner would see. Unfinished work is in Editorial, where it can be\nworked on.\n\n`editable` is the other question, asked by the pickers that offer an\narticle to link something to: not \"what may I read\" but \"what may I\nchange\". It answers with exactly what `can_edit_article` would allow, so a\npicker cannot offer an article the save would refuse \u2014 which is what a\ngrant holder got when this only ever returned the published shelf: their\nown unfinished drafts were missing from it, and everybody else's finished\narticles were in it.", "operationId": "list_articles_api_v1_articles__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "category_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}}, {"name": "q", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Q"}}, {"name": "editable", "in": "query", "required": false, "schema": {"type": "boolean", "description": "Articles this person may edit \u2014 their branch, drafts included \u2014 instead of the published library.", "default": false, "title": "Editable"}, "description": "Articles this person may edit \u2014 their branch, drafts included \u2014 instead of the published library."}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "post": {"tags": ["articles"], "summary": "Create Article", "operationId": "create_article_api_v1_articles__post", "security": [{"OAuth2PasswordBearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticleWrite"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/ai-sources": {"get": {"tags": ["articles"], "summary": "Ai Sources", "description": "Which grounding a draft can be offered here.\n\nAsked by the panel before it draws its checkboxes: a switch for a library\nnobody has configured is a switch that does nothing, and finding that out\nby ticking it and waiting is the worst way to learn it.", "operationId": "ai_sources_api_v1_articles_ai_sources_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/articles/link-targets": {"get": {"tags": ["articles"], "summary": "Link Targets", "description": "Articles and their sections, for the picker that writes a cross-reference.\n\nIts own endpoint rather than the listing, because the two want opposite\nthings: the listing is a page of cards and deliberately does not carry\nsections, and this needs the sections and almost nothing else. Eight\nresults, because a picker is for choosing rather than for browsing.\n\nOpen to anybody who may write an article, not just moderators. It was\nmoderator-only, so a grant holder editing their own article opened the\npicker, typed, and was told \"Nothing matches that\" for every term in the\nlibrary \u2014 the one control in the editor that silently returned an empty\nlist rather than refusing out loud.\n\nWhat comes back is narrower for them, though, and deliberately: the\npublished library, plus their own branch's unpublished drafts. A\ncross-reference is for a reader, so pointing at any published article is\nright; handing out every draft title in the library is not, and this is\nthe only article route that had no status filter at all.", "operationId": "link_targets_api_v1_articles_link_targets_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "q", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "What the writer typed", "title": "Q"}, "description": "What the writer typed"}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 25, "default": 8, "title": "Limit"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/recent": {"get": {"tags": ["articles"], "summary": "Recently Viewed", "description": "Articles this learner opened most recently, newest first.", "operationId": "recently_viewed_api_v1_articles_recent_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 20, "minimum": 1, "default": 5, "title": "Limit"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/by-slug/{slug}": {"get": {"tags": ["articles"], "summary": "Article By Slug", "description": "Resolve a slug \u2014 current or historical \u2014 to the article it names.", "operationId": "article_by_slug_api_v1_articles_by_slug__slug__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "slug", "in": "path", "required": true, "schema": {"type": "string", "title": "Slug"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/preview/{slug}": {"get": {"tags": ["articles"], "summary": "Preview Article", "description": "What a cross-reference points at, for a hover card.\n\nDeliberately small: a link preview that fetched whole articles would pull\ndown the library a paragraph at a time as somebody reads.", "operationId": "preview_article_api_v1_articles_preview__slug__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "slug", "in": "path", "required": true, "schema": {"type": "string", "title": "Slug"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/trash": {"get": {"tags": ["articles"], "summary": "List Trash", "description": "What has been deleted and can still be brought back.\n\nOnly ever articles that were published at some point: a draft nobody saw is\ndeleted outright, because there is nothing to restore and a trash full of\nabandoned stubs is a second list to maintain.", "operationId": "list_trash_api_v1_articles_trash_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/articles/{article_id}/restore": {"post": {"tags": ["articles"], "summary": "Restore Article", "description": "Out of the trash, in the state it went in.", "operationId": "restore_article_api_v1_articles__article_id__restore_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/trash/{article_id}": {"delete": {"tags": ["articles"], "summary": "Purge Article", "description": "Gone for good, and only from the trash \u2014 so it is always the second\ndecision about the same article rather than the first.", "operationId": "purge_article_api_v1_articles_trash__article_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/read": {"get": {"tags": ["articles"], "summary": "My Reads", "description": "The articles this learner has read, newest first.", "operationId": "my_reads_api_v1_articles_read_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/articles/{article_id}/read": {"post": {"tags": ["articles"], "summary": "Mark Read", "description": "Record, or take back, that this learner has read the article to its end.", "operationId": "mark_read_api_v1_articles__article_id__read_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}, {"name": "read", "in": "query", "required": false, "schema": {"type": "boolean", "default": true, "title": "Read"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}": {"get": {"tags": ["articles"], "summary": "Get Article", "operationId": "get_article_api_v1_articles__article_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "patch": {"tags": ["articles"], "summary": "Update Article", "operationId": "update_article_api_v1_articles__article_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticleWrite"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["articles"], "summary": "Delete Article", "operationId": "delete_article_api_v1_articles__article_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/notes": {"get": {"tags": ["articles"], "summary": "Read Section Notes", "description": "This reader's own notes on this article, section by section.\n\nPrivate, and only ever this reader's. Feedback to whoever maintains the\narticle is a different route with a different table \u2014 a note somebody else\ncan read is not the thing a learner thought they were writing.\n\nA note whose section has gone comes back marked `orphaned` rather than being\ndropped. Section ids survive a rename, so a retitled heading keeps its\nnotes; a deleted one leaves writing with nowhere to sit, and quietly\ndiscarding it is the one outcome worth engineering against. The reader shows\nthem at the end of the article under the heading they were written on.", "operationId": "read_section_notes_api_v1_articles__article_id__notes_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/notes/{section_id}": {"put": {"tags": ["articles"], "summary": "Write Section Note", "description": "Save or clear one section's note. Empty means delete, as for a question.", "operationId": "write_section_note_api_v1_articles__article_id__notes__section_id__put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}, {"name": "section_id", "in": "path", "required": true, "schema": {"type": "string", "title": "Section Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SectionNoteIn"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["articles"], "summary": "Delete Section Note", "description": "The writer's own delete \u2014 the only thing that removes a note on purpose.", "operationId": "delete_section_note_api_v1_articles__article_id__notes__section_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}, {"name": "section_id", "in": "path", "required": true, "schema": {"type": "string", "title": "Section Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/questions": {"get": {"tags": ["articles"], "summary": "Article Questions", "operationId": "article_questions_api_v1_articles__article_id__questions_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/decks": {"get": {"tags": ["articles"], "summary": "Article Decks", "description": "The card decks that belong with this article.\n\nDecks, not cards. A deck is the unit a learner studies and the unit the\nbank files: this used to list every individual card tied to the article,\nwhich turned the foot of a reading page into a dump of fronts and backs\nnobody came for. The user: \"You don't come to an article and start listing\nevery card.\"\n\nRelated two ways, and the second is why this is not a single join: a deck\n*linked* to this article belongs on its page whatever it is filed under,\nand a deck *filed* in this article's category belongs there too, which is\nhow a hand-written deck finds its reading without anybody linking it.\nFiling can only ever name one category, so it could not express a deck\nbuilt from two articles in different branches \u2014 see `services.deck_articles`.", "operationId": "article_decks_api_v1_articles__article_id__decks_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/publish": {"post": {"tags": ["articles"], "summary": "Publish Article", "operationId": "publish_article_api_v1_articles__article_id__publish_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticlePublish"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/links": {"put": {"tags": ["articles"], "summary": "Link Question", "description": "Say that this question is read with this article.\n\nBoth ends, or neither. Saying a question is read with an article is a\nstatement about the two of them, so it takes the right to change both:\na branch grant that covered only the article would let its holder attach\nsomebody else's questions to their own reading, and one that covered only\nthe question would let them write themselves into a library they do not\nhold. A moderator passes both checks and notices nothing.\n\nUnlinking stays moderator-only. This is the asymmetry it looks like, and\nit is deliberate: a link is not versioned \u2014 no revision, no change to the\narticle's `updated_at` \u2014 so removing one leaves nothing behind that says\nit existed. Adding one is visible in both panels the moment it is made.", "operationId": "link_question_api_v1_articles__article_id__links_put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticleLinkIn"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/aliases": {"put": {"tags": ["articles"], "summary": "Replace Aliases", "description": "Replace the cross-reference vocabulary wholesale.\n\nWritten by reading the whole catalogue at once \u2014 which article should own\n\"RDS\", which abbreviations are ambiguous enough to leave alone \u2014 and that\nis a reading job rather than a form-filling one. So it arrives as a file\nand lands in one call, rather than being typed in an admin page one row at\na time.\n\nReplaced, not merged: an alias removed from the file is an alias somebody\ndecided against, and a merge would keep it for ever with no way to say so.", "operationId": "replace_aliases_api_v1_articles_aliases_put", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AliasSet"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/articles/{article_id}/crosslinks": {"get": {"tags": ["articles"], "summary": "Propose Crosslinks", "description": "What this article's prose could point at. Writes nothing.\n\nThe whole proposal is returned so it can be read before any of it is\napplied \u2014 a pass that rewrites prose on its own judgement is a pass that\ncorrupts a day's writing in a way nobody notices until a reader does.", "operationId": "propose_crosslinks_api_v1_articles__article_id__crosslinks_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}, {"name": "per_section", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 20, "minimum": 1, "default": 6, "title": "Per Section"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "post": {"tags": ["articles"], "summary": "Apply Crosslinks", "description": "Write the approved cross-references into the prose.\n\nA revision is snapshotted first, so this is reversible from the editor like\nany other edit. Each proposal is matched again at the moment of writing: an\noffset is only true of the text it was measured against, and a term that\nhas moved is dropped rather than written at the wrong place.", "operationId": "apply_crosslinks_api_v1_articles__article_id__crosslinks_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CrosslinkApply"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/links/from-category": {"get": {"tags": ["articles"], "summary": "Preview Category Link", "description": "How many questions claiming this topic would add, before it is claimed.\n\nAsked separately so the button can carry the number. \"Link 43 questions\" is\na decision; \"Link this topic\" is a guess, and the difference matters when\nthe topic turns out to be the whole of Cardiology.", "operationId": "preview_category_link_api_v1_articles__article_id__links_from_category_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}, {"name": "category_id", "in": "query", "required": true, "schema": {"type": "integer", "title": "Category Id"}}, {"name": "section_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Section Id"}}, {"name": "include_subtopics", "in": "query", "required": false, "schema": {"type": "boolean", "default": true, "title": "Include Subtopics"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "post": {"tags": ["articles"], "summary": "Link Category", "description": "Claim a topic: every question in it links to this article, now and later.\n\nNot a one-off copy. \"The Cardiology article covers the Cardiology\nquestions\" is a standing statement about the material, and a copy of it\nstops being true the first time somebody files a new question \u2014 silently,\nwith nothing on any screen to say so.\n\nWhat is stored is the claim; what everything *reads* is still\n`question_article_links`, whose rows this makes. See\n`services/topic_claims.py` for why it is built that way round.", "operationId": "link_category_api_v1_articles__article_id__links_from_category_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticleCategoryLinkIn"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/claims": {"get": {"tags": ["articles"], "summary": "List Claims", "description": "The topics this article stands on, and how many questions each brings.", "operationId": "list_claims_api_v1_articles__article_id__claims_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/claims/{claim_id}": {"delete": {"tags": ["articles"], "summary": "Drop Claim", "description": "Stop claiming a topic.\n\nThe links it made stay by default, because they are ordinary links and\nsomebody may have curated them since. `keep_links=false` removes the ones\nthis claim would make \u2014 the \"undo the whole thing\" case.", "operationId": "drop_claim_api_v1_articles__article_id__claims__claim_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}, {"name": "claim_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Claim Id"}}, {"name": "keep_links", "in": "query", "required": false, "schema": {"type": "boolean", "default": true, "title": "Keep Links"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/links/{question_id}": {"delete": {"tags": ["articles"], "summary": "Unlink Question", "operationId": "unlink_question_api_v1_articles__article_id__links__question_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}, {"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}, {"name": "section_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Section Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/ai-draft": {"post": {"tags": ["articles"], "summary": "Start Article Draft", "description": "Queue an AI draft; the saved article is a draft until an educator publishes it.", "operationId": "start_article_draft_api_v1_articles_ai_draft_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticleAIDraft"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/articles/{article_id}/ai-refine": {"post": {"tags": ["articles"], "summary": "Start Article Refine", "operationId": "start_article_refine_api_v1_articles__article_id__ai_refine_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticleAIRefine"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/ai-cards": {"post": {"tags": ["articles"], "summary": "Start Article Cards", "description": "Generate cards into a private deck; sharing remains an explicit educator action.\n\nModerators and administrators. This said admin while the button offering\nit was drawn for every moderator, so every moderator who pressed it got\n\"Could not start AI job\" and no reason; the docstring called admin \"like\nevery other way of making a deck\", which `POST /flashcards/manual` had\nalready stopped being.\n\nReading the article is not enough, a grant over it is not enough, and a\ndeck grant is not enough either: a deck grant says \"look after these\ndecks\", not \"add another one to the tree\". Creating is a decision about\nwhat the bank contains, and it stays where the other two do.\n\nThe article must have a category. The deck inherits it, and a deck with no\ncategory is filed nowhere, reachable by no grant and findable by nobody\nbrowsing the tree \u2014 `POST /flashcards/manual` refuses one for exactly this\nreason and this path used to create them silently.", "operationId": "start_article_cards_api_v1_articles__article_id__ai_cards_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/illustrate": {"post": {"tags": ["articles"], "summary": "Start Article Illustration", "description": "Ask for a diagram on each long section that has none.\n\nMost sections are prose and get nothing, which is the intended outcome \u2014\na picture of a paragraph is worse than the paragraph. A revision is kept\nbefore anything changes, so one article's figures can be taken back out.", "operationId": "start_article_illustration_api_v1_articles__article_id__illustrate_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/job/{job_id}": {"get": {"tags": ["articles"], "summary": "Get Article Job", "description": "Poll an article AI job for the current user.", "operationId": "get_article_job_api_v1_articles_job__job_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "job_id", "in": "path", "required": true, "schema": {"type": "string", "title": "Job Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/status": {"post": {"tags": ["articles"], "summary": "Set Article Status", "description": "Move an article through draft \u2192 in review \u2192 published.\n\nEditorial work, so an editor does it. It used to let an author move their\nown article along, which was the last place authorship still bought\nanything \u2014 and an article has no author now: it belongs to the library.", "operationId": "set_article_status_api_v1_articles__article_id__status_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusIn"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/revisions": {"get": {"tags": ["articles"], "summary": "List Revisions", "operationId": "list_revisions_api_v1_articles__article_id__revisions_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/revisions/{revision_id}": {"get": {"tags": ["articles"], "summary": "Read Revision", "operationId": "read_revision_api_v1_articles__article_id__revisions__revision_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}, {"name": "revision_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Revision Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/revisions/{revision_id}/restore": {"post": {"tags": ["articles"], "summary": "Restore Revision", "description": "Put an old version back, keeping the current one as a revision of its own.\n\nRestoring is itself an edit, so it is snapshotted too \u2014 otherwise the way\nback from a mistaken restore is gone.", "operationId": "restore_revision_api_v1_articles__article_id__revisions__revision_id__restore_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}, {"name": "revision_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Revision Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/editorial/queue": {"get": {"tags": ["articles"], "summary": "Editorial Queue", "description": "What still needs a person: the work, not a list of everything.\n\nEach bucket is something an editor can act on today. Counting articles by\nstatus alone would say how many exist, which is not a queue.", "operationId": "editorial_queue_api_v1_articles_editorial_queue_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/articles/{article_id}/figures": {"post": {"tags": ["articles"], "summary": "Add Article Figure", "description": "Render a diagram spec, store it, and hang it on a section.", "operationId": "add_article_figure_api_v1_articles__article_id__figures_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/app__routers__articles__FigureIn"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "get": {"tags": ["articles"], "summary": "List Article Figures", "description": "The figures on this article, with the specs they were drawn from.\n\nThe way to re-render a batch when the geometry improves, or to read back\nwhat a run actually produced: `kind` across a batch is the number that\ntold us the first three hundred were all the same diagram.", "operationId": "list_article_figures_api_v1_articles__article_id__figures_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/figures/image": {"post": {"tags": ["articles"], "summary": "Add Article Image", "description": "Attach a teaching image or a short clip \u2014 the raster twin of the spec route.\n\nA drawing this site generates is an SVG rendered from a spec; a clinical\nsign, an exanthem or an anatomical plate is a picture, and no amount of\ngeometry will produce one. Some things are neither, because they are a\nsequence \u2014 the heart's chambers dividing, a midgut loop rotating \u2014 and\nthose arrive as a short silent clip. All three end up in an article the\nsame way.", "operationId": "add_article_image_api_v1_articles__article_id__figures_image_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "requestBody": {"required": true, "content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_add_article_image_api_v1_articles__article_id__figures_image_post"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/figures/existing": {"post": {"tags": ["articles"], "summary": "Place Existing Figure", "description": "Show a figure the bank already holds in another section of this article.\n\nThe same pathway diagram belongs in the clinical view and in the matching\nlong section. Attaching it twice would make two of everything \u2014 two assets,\ntwo objects in an un-versioned bucket, two search results \u2014 and a\ncorrection that has to be made twice.", "operationId": "place_existing_figure_api_v1_articles__article_id__figures_existing_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PlaceFigure"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/articles/{article_id}/figures/{asset_id}": {"delete": {"tags": ["articles"], "summary": "Remove Article Figure", "description": "Take a figure back out: the line, the link, and \u2014 unless `keep_asset` \u2014 the\nasset row and the stored object with it.\n\nNeither form destroys anything now: the binning one puts the picture in the\nimage library's own thirty-day bin, where it can be restored, and the\nscheduled purge is the only code that removes bytes.\n\nA grant holder still only ever unlinks. Not because binning is dangerous\nany more, but because an article grant is not a grant over the library the\npicture lives in \u2014 the same picture may hang on three other articles and\nbelong to somebody else's shelf. \"Take this figure off my article\" is what\nthey mean, and it is what they get.", "operationId": "remove_article_figure_api_v1_articles__article_id__figures__asset_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}, {"name": "asset_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Asset Id"}}, {"name": "keep_asset", "in": "query", "required": false, "schema": {"type": "boolean", "description": "Unlink only: drop the figure from this article but leave the image in the library and its bytes in storage.", "default": false, "title": "Keep Asset"}, "description": "Unlink only: drop the figure from this article but leave the image in the library and its bytes in storage."}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/glossary": {"get": {"tags": ["glossary"], "summary": "List Terms", "operationId": "list_terms_api_v1_glossary_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "q", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Filter by the start of a term", "title": "Q"}, "description": "Filter by the start of a term"}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/TermOut"}, "title": "Response List Terms Api V1 Glossary Get"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "post": {"tags": ["glossary"], "summary": "Put Term", "operationId": "put_term_api_v1_glossary_post", "security": [{"OAuth2PasswordBearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TermIn"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TermOut"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/glossary/{key}": {"get": {"tags": ["glossary"], "summary": "Get Term", "operationId": "get_term_api_v1_glossary__key__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "key", "in": "path", "required": true, "schema": {"type": "string", "title": "Key"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TermOut"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["glossary"], "summary": "Remove Term", "operationId": "remove_term_api_v1_glossary__key__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "key", "in": "path", "required": true, "schema": {"type": "string", "title": "Key"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/glossary/bulk": {"post": {"tags": ["glossary"], "summary": "Put Many", "description": "A batch, for a pipeline that generates definitions rather than types them.\n\nAll or nothing: a batch that fails halfway would leave the glossary in a\nstate nobody chose, and the caller cannot tell which half landed.", "operationId": "put_many_api_v1_glossary_bulk_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/BulkIn"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BulkOut"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/access/": {"get": {"tags": ["access"], "summary": "Access Overview", "description": "Everyone who can be given access, and what each of them holds.", "operationId": "access_overview_api_v1_access__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/access/tree": {"get": {"tags": ["access"], "summary": "Access Tree", "description": "What there is to grant: the category tree, the image libraries, the folders.", "operationId": "access_tree_api_v1_access_tree_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/access/changes": {"get": {"tags": ["access"], "summary": "Recent Changes", "description": "Who changed what, newest first \u2014 questions and articles together.\n\nNothing new is recorded for this: every question edit already snapshots\nthe question into `question_versions` and every article edit writes an\n`article_revisions` row, both stamped with the author and the time. What\nwas missing was somewhere to read them, so handing somebody an editorial\ngrant meant trusting it and finding out later.\n\nA snapshot is the state *before* the edit, so a row means \"this person\nchanged this thing at this time\" rather than carrying a diff. The title\nshown is the thing's title now, which is what you would search for.", "operationId": "recent_changes_api_v1_access_changes_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 500, "default": 100, "title": "Limit"}}, {"name": "user_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "Only this person's edits", "title": "User Id"}, "description": "Only this person's edits"}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/access/{user_id}/grants": {"post": {"tags": ["access"], "summary": "Add Grant", "operationId": "add_grant_api_v1_access__user_id__grants_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "user_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "User Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/GrantWrite"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/access/{user_id}/grants/{kind}/{target_id}": {"delete": {"tags": ["access"], "summary": "Remove Grant", "operationId": "remove_grant_api_v1_access__user_id__grants__kind___target_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "user_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "User Id"}}, {"name": "kind", "in": "path", "required": true, "schema": {"type": "string", "title": "Kind"}}, {"name": "target_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Target Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/access/{user_id}/role": {"put": {"tags": ["access"], "summary": "Set Role", "description": "Give or take away \"everything\".\n\nThis is a role change, not a grant, and the two are kept distinct on\npurpose: a wildcard grant that silently meant everything would be the kind\nof permission nobody can audit.", "operationId": "set_role_api_v1_access__user_id__role_put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "user_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "User Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RoleWrite"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/feedback/questions/{question_id}": {"post": {"tags": ["feedback"], "summary": "Leave Feedback", "description": "Report something about a question. Any signed-in learner may.", "operationId": "leave_feedback_api_v1_feedback_questions__question_id__post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FeedbackIn"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "get": {"tags": ["feedback"], "summary": "Feedback For Question", "description": "Every report about one question, for the educator editing it.", "operationId": "feedback_for_question_api_v1_feedback_questions__question_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/feedback/questions/{question_id}/mine": {"get": {"tags": ["feedback"], "summary": "My Feedback On Question", "description": "What this learner has already reported here, and what came back.\n\nWithout it a report went into silence: an educator could reply and resolve,\nand the person who wrote in had no way of ever seeing that they had. It is\nread from the question rather than from a list of one's own reports because\nthat is where the learner is when they wonder.", "operationId": "my_feedback_on_question_api_v1_feedback_questions__question_id__mine_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/feedback/open": {"get": {"tags": ["feedback"], "summary": "Open Feedback", "description": "What is outstanding \u2014 the count for a badge, and the newest few.\n\nQuestions and articles in one list. They are the same job to whoever is\ndoing it, and a second badge for reading would be a second place to forget\nto look.\n\nAnswers quietly for someone with no editorial access rather than refusing,\nso the header can ask for it without first asking who is asking.", "operationId": "open_feedback_api_v1_feedback_open_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 50, "minimum": 1, "default": 8, "title": "Limit"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/feedback/{feedback_id}": {"patch": {"tags": ["feedback"], "summary": "Answer Feedback", "description": "Reply to a report, resolve it, or reopen it.", "operationId": "answer_feedback_api_v1_feedback__feedback_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "feedback_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Feedback Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ReplyIn"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["feedback"], "summary": "Delete Feedback", "operationId": "delete_feedback_api_v1_feedback__feedback_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "feedback_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Feedback Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/feedback/articles/{article_id}": {"post": {"tags": ["feedback"], "summary": "Leave Article Feedback", "description": "Report something about an article, or about one section of it.", "operationId": "leave_article_feedback_api_v1_feedback_articles__article_id__post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticleFeedbackIn"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "get": {"tags": ["feedback"], "summary": "Feedback For Article", "description": "Every report about one article, for whoever maintains it.", "operationId": "feedback_for_article_api_v1_feedback_articles__article_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/feedback/articles/{article_id}/mine": {"get": {"tags": ["feedback"], "summary": "My Feedback On Article", "description": "This reader's own reports on this article, and any replies.", "operationId": "my_feedback_on_article_api_v1_feedback_articles__article_id__mine_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/feedback/articles/reports/{feedback_id}": {"patch": {"tags": ["feedback"], "summary": "Answer Article Feedback", "operationId": "answer_article_feedback_api_v1_feedback_articles_reports__feedback_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "feedback_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Feedback Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ReplyIn"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["feedback"], "summary": "Delete Article Feedback", "operationId": "delete_article_feedback_api_v1_feedback_articles_reports__feedback_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "feedback_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Feedback Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/folders/": {"get": {"tags": ["folders"], "summary": "List Folders", "operationId": "list_folders_api_v1_folders__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "post": {"tags": ["folders"], "summary": "Create Folder", "operationId": "create_folder_api_v1_folders__post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/FolderWrite"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/folders/{folder_id}": {"patch": {"tags": ["folders"], "summary": "Rename Folder", "operationId": "rename_folder_api_v1_folders__folder_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "folder_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Folder Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FolderWrite"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["folders"], "summary": "Delete Folder", "description": "Delete a folder, and with it every grant that pointed at it.\n\nThe grants go by the foreign key's cascade rather than being left dangling:\na grant naming nothing grants nothing, so an orphan row would quietly become\na grant that covers no questions and reads on the access page as though the\nperson still holds something.", "operationId": "delete_folder_api_v1_folders__folder_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "folder_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Folder Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/folders/{folder_id}/questions": {"get": {"tags": ["folders"], "summary": "Folder Questions", "operationId": "folder_questions_api_v1_folders__folder_id__questions_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "folder_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Folder Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "post": {"tags": ["folders"], "summary": "Add Questions", "description": "Put questions in. Only ones this educator already manages.\n\nWithout that check a non-moderator could file a question they cannot reach\ninto a folder and then be handed a grant over it \u2014 access laundered through\na list they wrote themselves.", "operationId": "add_questions_api_v1_folders__folder_id__questions_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "folder_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Folder Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FolderQuestions"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/folders/{folder_id}/questions/{question_id}": {"delete": {"tags": ["folders"], "summary": "Remove Question", "operationId": "remove_question_api_v1_folders__folder_id__questions__question_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "folder_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Folder Id"}}, {"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/exams/": {"get": {"tags": ["exams"], "summary": "List Exams", "description": "Every selectable exam, with how many bank questions it covers.", "operationId": "list_exams_api_v1_exams__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "post": {"tags": ["exams"], "summary": "Create Exam", "description": "Create an objective, with everything it was given.\n\nThis used to write name, slug, sort order and active, and silently drop\nfamily, description and article views \u2014 so a new objective landed in\n\"Other\" showing every view, whatever was asked for.", "operationId": "create_exam_api_v1_exams__post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ExamWrite"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/exams/active": {"put": {"tags": ["exams"], "summary": "Set Active Exam", "description": "Switch which exam the learner is studying for. Persisted on the user.", "operationId": "set_active_exam_api_v1_exams_active_put", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ActiveExam"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/exams/{exam_id}": {"patch": {"tags": ["exams"], "summary": "Update Exam", "operationId": "update_exam_api_v1_exams__exam_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "exam_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Exam Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ExamEdit"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/exams/{exam_id}/assign": {"post": {"tags": ["exams"], "summary": "Assign By Category", "description": "Put everything under these categories into this objective.\n\nPopulating a new objective one link row at a time is not a thing anyone\nwould do for three thousand questions. Adding is idempotent \u2014 running it\ntwice adds nothing the second time \u2014 so it is safe to widen a selection\nand run it again.", "operationId": "assign_by_category_api_v1_exams__exam_id__assign_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "exam_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Exam Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AssignByCategory"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["exams"], "summary": "Unassign By Category", "description": "Take them back out. The questions and articles are untouched \u2014 only\ntheir membership of this objective goes.", "operationId": "unassign_by_category_api_v1_exams__exam_id__assign_delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "exam_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Exam Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AssignByCategory"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/exams/{exam_id}/blueprint": {"get": {"tags": ["exams"], "summary": "Read Blueprint", "description": "The published shape of this exam, and what the bank can supply for it.", "operationId": "read_blueprint_api_v1_exams__exam_id__blueprint_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "exam_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Exam Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/exams/{exam_id}/blueprint/{blueprint_id}/categories": {"put": {"tags": ["exams"], "summary": "Set Blueprint Categories", "description": "Which of our categories feed one line of the outline.\n\nReplaces rather than adds: the mapping is a statement of what this domain\nis, and editing it by hand should not require remembering what was there.", "operationId": "set_blueprint_categories_api_v1_exams__exam_id__blueprint__blueprint_id__categories_put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "exam_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Exam Id"}}, {"name": "blueprint_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Blueprint Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BlueprintCategories"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/me/exam-results": {"get": {"tags": ["exam results"], "summary": "List Results", "description": "Every exam this person has recorded, most recent sitting first.", "operationId": "list_results_api_v1_me_exam_results_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 500, "minimum": 1, "default": 100, "title": "Limit"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "post": {"tags": ["exam results"], "summary": "Add Result", "operationId": "add_result_api_v1_me_exam_results_post", "security": [{"OAuth2PasswordBearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResultIn"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/me/exam-results/{result_id}": {"patch": {"tags": ["exam results"], "summary": "Edit Result", "description": "Correct one. A score report is read off a screen and mistyped.", "operationId": "edit_result_api_v1_me_exam_results__result_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "result_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Result Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResultEdit"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["exam results"], "summary": "Remove Result", "operationId": "remove_result_api_v1_me_exam_results__result_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "result_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Result Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-plans/": {"get": {"tags": ["study-plans"], "summary": "List Study Plans", "description": "Published plans, with how far this learner has got through each.", "operationId": "list_study_plans_api_v1_study_plans__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "post": {"tags": ["study-plans"], "summary": "Create Plan", "operationId": "create_plan_api_v1_study_plans__post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PlanWrite"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/study-plans/{plan_id}": {"get": {"tags": ["study-plans"], "summary": "Get Study Plan", "operationId": "get_study_plan_api_v1_study_plans__plan_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "plan_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Plan Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "patch": {"tags": ["study-plans"], "summary": "Update Plan", "operationId": "update_plan_api_v1_study_plans__plan_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "plan_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Plan Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PlanUpdate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["study-plans"], "summary": "Delete Plan", "description": "Remove a plan and everything of its own: blocks, their reading, learners'\nreads and progress. Attempts already sat keep their sessions (the quiz\nlink is set null, not removed). Admin only, API only.", "operationId": "delete_plan_api_v1_study_plans__plan_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "plan_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Plan Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-plans/blocks/{block_id}/start": {"post": {"tags": ["study-plans"], "summary": "Start Block", "description": "Turn a block into a test. Reuses the learner's existing quiz for the block.\n\nWithout the reuse, reopening a block would create a duplicate test each time\nand scatter the learner's attempts across them.", "operationId": "start_block_api_v1_study_plans_blocks__block_id__start_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "block_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Block Id"}}, {"name": "mode", "in": "query", "required": false, "schema": {"type": "string", "default": "learning", "title": "Mode"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-plans/reading/{link_id}/read": {"post": {"tags": ["study-plans"], "summary": "Mark Reading", "description": "Mark one of a block's articles as read, or take that back.\n\nReversible on purpose: a learner who ticks the wrong row should be able to\ncorrect it without an educator, and progress nobody can correct stops being\ntrusted and then stops being used.", "operationId": "mark_reading_api_v1_study_plans_reading__link_id__read_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "link_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Link Id"}}, {"name": "read", "in": "query", "required": false, "schema": {"type": "boolean", "default": true, "title": "Read"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-plans/from-blueprint": {"post": {"tags": ["study-plans"], "summary": "Create From Blueprint", "description": "Build a plan to an examining board's published content outline.\n\nTwo shapes, because a plan is asked to do two different things. Papers are\nrehearsal: each block is drawn to the board's weights, so sitting one tells\na learner something about how they would do on the day. Domains are study:\none block per content area, in the board's order and carrying its titles,\nwhich is how someone works through a subject rather than tests themselves\non all of it.\n\nMembership is snapshotted, as with any plan \u2014 a plan you are part-way\nthrough must not reshuffle between visits \u2014 and no question appears in two\nblocks of the same plan.", "operationId": "create_from_blueprint_api_v1_study_plans_from_blueprint_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/BlueprintPlan"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/study-plans/{plan_id}/blocks": {"post": {"tags": ["study-plans"], "summary": "Add Block", "operationId": "add_block_api_v1_study_plans__plan_id__blocks_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "plan_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Plan Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BlockWrite"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-plans/blocks/{block_id}": {"patch": {"tags": ["study-plans"], "summary": "Update Block", "operationId": "update_block_api_v1_study_plans_blocks__block_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "block_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Block Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BlockUpdate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["study-plans"], "summary": "Delete Block", "description": "Delete a block and close the gap it leaves in the numbering.", "operationId": "delete_block_api_v1_study_plans_blocks__block_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "block_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Block Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-plans/{plan_id}/blocks/order": {"post": {"tags": ["study-plans"], "summary": "Reorder Blocks", "description": "Set the order of a plan's blocks in one go.", "operationId": "reorder_blocks_api_v1_study_plans__plan_id__blocks_order_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "plan_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Plan Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BlockOrder"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-plans/blocks/{block_id}/move": {"post": {"tags": ["study-plans"], "summary": "Move Questions", "description": "Move questions from one block to another within the same plan.", "operationId": "move_questions_api_v1_study_plans_blocks__block_id__move_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "block_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Block Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MoveQuestions"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-plans/blocks/{block_id}/articles": {"post": {"tags": ["study-plans"], "summary": "Attach Article", "description": "Attach reading to a block, appended after whatever is already there.", "operationId": "attach_article_api_v1_study_plans_blocks__block_id__articles_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "block_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Block Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BlockArticleIn"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-plans/reading/{link_id}": {"delete": {"tags": ["study-plans"], "summary": "Detach Article", "operationId": "detach_article_api_v1_study_plans_reading__link_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "link_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Link Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/drafts/batches": {"get": {"tags": ["drafts"], "summary": "List Batches", "operationId": "list_batches_api_v1_drafts_batches_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "status", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Status"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/drafts/batches/{batch_id}": {"get": {"tags": ["drafts"], "summary": "Read Batch", "operationId": "read_batch_api_v1_drafts_batches__batch_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "batch_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Batch Id"}}, {"name": "status", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Status"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "patch": {"tags": ["drafts"], "summary": "Update Batch", "operationId": "update_batch_api_v1_drafts_batches__batch_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "batch_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Batch Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BatchEdit"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["drafts"], "summary": "Delete Batch", "description": "Throw a whole run away. Questions already accepted from it stay \u2014 they\nare in the bank now and are nothing to do with the batch any more.", "operationId": "delete_batch_api_v1_drafts_batches__batch_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "batch_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Batch Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/drafts/extract": {"post": {"tags": ["drafts"], "summary": "Extract To Drafts", "description": "Queue a section for extraction into a review-only draft batch.", "operationId": "extract_to_drafts_api_v1_drafts_extract_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DraftExtractionCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/drafts/extract/{job_id}": {"get": {"tags": ["drafts"], "summary": "Get Draft Extraction Job", "description": "Read a draft-extraction job's progress and final review-batch id.", "operationId": "get_draft_extraction_job_api_v1_drafts_extract__job_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "job_id", "in": "path", "required": true, "schema": {"type": "string", "title": "Job Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/drafts/import": {"post": {"tags": ["drafts"], "summary": "Import Spreadsheet", "description": "A CSV or XLSX, one question per row, as a batch of drafts.", "operationId": "import_spreadsheet_api_v1_drafts_import_post", "requestBody": {"content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_import_spreadsheet_api_v1_drafts_import_post"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/drafts/import/qti": {"post": {"tags": ["drafts"], "summary": "Import Qti", "description": "A QTI 2.1 export from another question bank, as a batch of drafts.", "operationId": "import_qti_api_v1_drafts_import_qti_post", "requestBody": {"content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_import_qti_api_v1_drafts_import_qti_post"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/drafts/import/sample": {"get": {"tags": ["drafts"], "summary": "Sample Spreadsheet", "description": "The columns an import reads, with two rows filled in.\n\nOpen, because it is a blank template and a plain `<a download>` carries no\ntoken. Gating it would make the one link on the panel 401 for the very\nmoderator allowed to use the panel.", "operationId": "sample_spreadsheet_api_v1_drafts_import_sample_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/v1/drafts/{draft_id}": {"patch": {"tags": ["drafts"], "summary": "Update Draft", "description": "Fix a draft in place. This is the whole point of the staging table: it\ncan be wrong, and be corrected, without anything being in the bank.", "operationId": "update_draft_api_v1_drafts__draft_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "draft_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Draft Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DraftEdit"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["drafts"], "summary": "Delete Draft", "description": "Permanently remove a dropped proposal, never a bank question.", "operationId": "delete_draft_api_v1_drafts__draft_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "draft_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Draft Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/drafts/accept": {"post": {"tags": ["drafts"], "summary": "Accept Drafts", "description": "Put drafts into the bank. Each one becomes a question here and nowhere\nelse, which is the only moment a question id is taken.\n\nAll or nothing: a batch half-accepted because the twentieth draft was\nmissing an answer is worse than a refusal naming it.", "operationId": "accept_drafts_api_v1_drafts_accept_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Decision"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/drafts/reject": {"post": {"tags": ["drafts"], "summary": "Reject Drafts", "operationId": "reject_drafts_api_v1_drafts_reject_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Decision"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/drafts/{draft_id}/reopen": {"post": {"tags": ["drafts"], "summary": "Reopen Draft", "operationId": "reopen_draft_api_v1_drafts__draft_id__reopen_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "draft_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Draft Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/media/libraries": {"get": {"tags": ["media"], "summary": "List Libraries", "description": "Libraries this user may use, with how many images each holds.", "operationId": "list_libraries_api_v1_media_libraries_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "post": {"tags": ["media"], "summary": "Create Library", "operationId": "create_library_api_v1_media_libraries_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LibraryIn"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/media/libraries/{library_id}/grants": {"post": {"tags": ["media"], "summary": "Grant Library", "operationId": "grant_library_api_v1_media_libraries__library_id__grants_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "library_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Library Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LibraryGrantIn"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/media/libraries/{library_id}/grants/{user_id}": {"delete": {"tags": ["media"], "summary": "Revoke Library", "operationId": "revoke_library_api_v1_media_libraries__library_id__grants__user_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "library_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Library Id"}}, {"name": "user_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "User Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/media/": {"get": {"tags": ["media"], "summary": "List Media", "description": "Browse the image bank. `q` searches captions, alt text and titles.\n\nOne collection, however a figure got here. A question's figure and a\ndiagram drawn for an article are the same kind of thing to anybody looking\nfor a picture of a topic, and keeping them in two grids meant reading\n\"440\" in one place and \"291\" in another and never seeing the bank. Where\nit came from is a filter, not a wall.", "operationId": "list_media_api_v1_media__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "q", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Q"}}, {"name": "library_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Library Id"}}, {"name": "source", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "question, article or unused", "title": "Source"}, "description": "question, article or unused"}, {"name": "managing", "in": "query", "required": false, "schema": {"type": "boolean", "description": "Only what this account may edit. What the media manager asks for: its cards carry Edit and its corner carries Upload, so listing everything readable is a page of refusals.", "default": false, "title": "Managing"}, "description": "Only what this account may edit. What the media manager asks for: its cards carry Edit and its corner carries Upload, so listing everything readable is a page of refusals."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 200, "default": 60, "title": "Limit"}}, {"name": "offset", "in": "query", "required": false, "schema": {"type": "integer", "default": 0, "title": "Offset"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/media/by-path": {"get": {"tags": ["media"], "summary": "Media By Path", "description": "What is known about the image at this path: label, description, source.\n\nThe reader's viewer asks for this when a figure is opened, not when the page\nis drawn \u2014 a page of prose with six figures in it should cost six requests\nonly if somebody opens all six.\n\nAnswers 404 rather than 403 for an image in a library this person cannot\nuse: whether a private library holds a given filename is not a question\nthis endpoint should answer.", "operationId": "media_by_path_api_v1_media_by_path_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "path", "in": "query", "required": true, "schema": {"type": "string", "title": "Path"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/media/upload": {"post": {"tags": ["media"], "summary": "Upload Media", "description": "Store an image and index it so it can be found by what it shows.", "operationId": "upload_media_api_v1_media_upload_post", "requestBody": {"content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_upload_media_api_v1_media_upload_post"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/media/{media_id}": {"patch": {"tags": ["media"], "summary": "Update Media", "description": "Edit an image's description and tags, and move it between libraries.", "operationId": "update_media_api_v1_media__media_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "media_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Media Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MediaUpdate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["media"], "summary": "Delete Media", "description": "Delete an image. Refused while a question still uses it, unless forced.\n\nA question refers to a figure by id, so renaming it or moving it between\nlibraries never breaks anything. Deleting does, and silently \u2014 the cascade\nwould take the link with it and the question would simply stop having a\npicture. So the count has to be faced first.", "operationId": "delete_media_api_v1_media__media_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "media_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Media Id"}}, {"name": "force", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Force"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/media/trash": {"get": {"tags": ["media"], "summary": "List Trashed Media", "description": "Images waiting to be either restored or forgotten.\n\nScoped by the same library rules as everything else: a person sees the\ndeleted images of the libraries they could see undeleted. Trash is not a\nway round a grant.", "operationId": "list_trashed_media_api_v1_media_trash_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/media/{media_id}/restore": {"post": {"tags": ["media"], "summary": "Restore Media", "description": "Put a deleted image back. The bytes never went anywhere.", "operationId": "restore_media_api_v1_media__media_id__restore_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "media_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Media Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/share/{token}": {"get": {"tags": ["share"], "summary": "Get Shared Quiz", "description": "Public quiz landing data; never exposes questions or answers.", "operationId": "get_shared_quiz_api_v1_share__token__get", "parameters": [{"name": "token", "in": "path", "required": true, "schema": {"type": "string", "title": "Token"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/share/questions/{token}": {"get": {"tags": ["share"], "summary": "Get Shared Questions", "description": "A handful of questions, opened by a link, by somebody with no account.\n\nNo authentication, deliberately. The point of the link is that a colleague\ncan look at the question without making an account, and asking them to sign\nin first is the friction the whole feature exists to remove.\n\nNothing here writes anything. There is no attempt, no session and no row,\nfor the visitor or for whoever sent it, and that holds whether or not the\nperson opening it is signed in \u2014 a shared question is a conversation\nbetween two people, not a sitting.\n\nThe answer and the explanation are in the payload rather than behind a\nsecond request. Everything in a shared set is deliberately public to\nwhoever holds the link, so withholding the answer would only be hiding it\nfrom the network tab, and it lets the player reveal instantly and keep\nworking on a bad connection.", "operationId": "get_shared_questions_api_v1_share_questions__token__get", "parameters": [{"name": "token", "in": "path", "required": true, "schema": {"type": "string", "title": "Token"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/share/questions": {"post": {"tags": ["share"], "summary": "Mint Shared Questions", "description": "A link to a few questions from a session you are sitting.\n\n**From inside a session, and study mode only.** The user's rule, and the\nshape they pointed at in AMBOSS: you are working through something, you\nfind a question worth arguing about, you send that one to a colleague.\nThere is no sharing from the bank \u2014 a link that could be made against any\nquestion in the bank is a way to publish the bank one URL at a time \u2014 and\nnone from an exam, where the questions are what somebody is about to be\nmarked on.\n\nNothing is written. The token *is* the set, so there is no row here to\nclean up, nothing that ties the link back to who made it, and no way for\nediting or deleting the session afterwards to change what the recipient\nopens. See `services/shared_set`.", "operationId": "mint_shared_questions_api_v1_share_questions_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MintRequest"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/collections/": {"get": {"tags": ["collections"], "summary": "List Collections", "description": "Every library this learner keeps, most recently used first.\n\nA never-opened library sorts by when it was made, beneath everything that\nhas been used \u2014 it is newer to the learner than it is to the database.", "operationId": "list_collections_api_v1_collections__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "post": {"tags": ["collections"], "summary": "Create Collection", "operationId": "create_collection_api_v1_collections__post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CollectionCreate"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/collections/{collection_id}": {"patch": {"tags": ["collections"], "summary": "Rename Collection", "operationId": "rename_collection_api_v1_collections__collection_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "collection_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Collection Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CollectionCreate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["collections"], "summary": "Delete Collection", "operationId": "delete_collection_api_v1_collections__collection_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "collection_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Collection Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/collections/{collection_id}/articles": {"get": {"tags": ["collections"], "summary": "Collection Articles", "operationId": "collection_articles_api_v1_collections__collection_id__articles_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "collection_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Collection Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/collections/{collection_id}/articles/{article_id}": {"put": {"tags": ["collections"], "summary": "Add Collection Article", "operationId": "add_collection_article_api_v1_collections__collection_id__articles__article_id__put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "collection_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Collection Id"}}, {"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["collections"], "summary": "Remove Collection Article", "operationId": "remove_collection_article_api_v1_collections__collection_id__articles__article_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "collection_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Collection Id"}}, {"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/collections/{collection_id}/images": {"get": {"tags": ["collections"], "summary": "Collection Images", "operationId": "collection_images_api_v1_collections__collection_id__images_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "collection_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Collection Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/collections/{collection_id}/images/{media_id}": {"put": {"tags": ["collections"], "summary": "Add Collection Image", "operationId": "add_collection_image_api_v1_collections__collection_id__images__media_id__put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "collection_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Collection Id"}}, {"name": "media_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Media Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["collections"], "summary": "Remove Collection Image", "operationId": "remove_collection_image_api_v1_collections__collection_id__images__media_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "collection_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Collection Id"}}, {"name": "media_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Media Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/collections/for-image/{media_id}": {"get": {"tags": ["collections"], "summary": "Libraries Holding Image", "description": "Which of this learner's libraries already hold this image.", "operationId": "libraries_holding_image_api_v1_collections_for_image__media_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "media_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Media Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/collections/for-article/{article_id}": {"get": {"tags": ["collections"], "summary": "Libraries Holding Article", "description": "Which of this learner's libraries already hold this article.\n\nThe reader needs it to draw the bookmark filled or hollow. Asked as one\nquestion rather than by fetching every library's contents, which would also\nstamp each of them as used and reorder the learner's own list.", "operationId": "libraries_holding_article_api_v1_collections_for_article__article_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/documents/upload": {"post": {"tags": ["documents"], "summary": "Upload Document", "operationId": "upload_document_api_v1_documents_upload_post", "requestBody": {"content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_upload_document_api_v1_documents_upload_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DocumentResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/documents/": {"get": {"tags": ["documents"], "summary": "List Documents", "operationId": "list_documents_api_v1_documents__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/DocumentResponse"}, "type": "array", "title": "Response List Documents Api V1 Documents  Get"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/documents/{document_id}": {"get": {"tags": ["documents"], "summary": "Get Document", "operationId": "get_document_api_v1_documents__document_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "document_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Document Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DocumentResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["documents"], "summary": "Delete Document", "operationId": "delete_document_api_v1_documents__document_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "document_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Document Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/documents/{document_id}/status": {"get": {"tags": ["documents"], "summary": "Get Document Status", "operationId": "get_document_status_api_v1_documents__document_id__status_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "document_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Document Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DocumentStatusResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/documents/{document_id}/processing-steps": {"get": {"tags": ["documents"], "summary": "Get Processing Steps", "description": "Poll PDF processing progress steps from Redis.", "operationId": "get_processing_steps_api_v1_documents__document_id__processing_steps_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "document_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Document Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/documents/{document_id}/sections": {"post": {"tags": ["documents"], "summary": "Create Section", "operationId": "create_section_api_v1_documents__document_id__sections_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "document_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Document Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SectionCreate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SectionResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/documents/{document_id}/sections/{section_id}": {"delete": {"tags": ["documents"], "summary": "Delete Section", "operationId": "delete_section_api_v1_documents__document_id__sections__section_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "document_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Document Id"}}, {"name": "section_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Section Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/": {"get": {"tags": ["quizzes"], "summary": "List Quizzes", "description": "List accessible general quizzes (owned, published, or shared).", "operationId": "list_quizzes_api_v1_quizzes__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/QuizResponse"}, "type": "array", "title": "Response List Quizzes Api V1 Quizzes  Get"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "post": {"tags": ["quizzes"], "summary": "Create Quiz", "description": "Start draft-only extraction; poll /quizzes/job/{job_id} for its batch.", "operationId": "create_quiz_api_v1_quizzes__post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/QuizCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/quizzes/jobs": {"get": {"tags": ["quizzes"], "summary": "List User Jobs", "description": "Return all recent extraction jobs for the logged-in user (any browser/session).", "operationId": "list_user_jobs_api_v1_quizzes_jobs_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/quizzes/jobs/{job_id}": {"delete": {"tags": ["quizzes"], "summary": "Forget Job", "description": "Take one job off this person's list.\n\nA job is a record of work, not the work itself: removing it stops nothing\nthat is running and deletes nothing it produced. It exists because the list\nis a list of what happened, and one somebody has read and dealt with is\nclutter in front of the ones they have not.\n\nOnly from your own list \u2014 the id alone is not authority to touch anybody\nelse's, and the keys behind it are shared.", "operationId": "forget_job_api_v1_quizzes_jobs__job_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "job_id", "in": "path", "required": true, "schema": {"type": "string", "title": "Job Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/job/{job_id}": {"get": {"tags": ["quizzes"], "summary": "Get Extraction Job", "description": "Poll extraction job progress. Returns the steps, the status, and \u2014 when it\nfinishes \u2014 the review batch the drafts landed in.", "operationId": "get_extraction_job_api_v1_quizzes_job__job_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "job_id", "in": "path", "required": true, "schema": {"type": "string", "title": "Job Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/job/{job_id}/cancel": {"post": {"tags": ["quizzes"], "summary": "Cancel Extraction Job", "description": "Cancel a running extraction job.", "operationId": "cancel_extraction_job_api_v1_quizzes_job__job_id__cancel_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "job_id", "in": "path", "required": true, "schema": {"type": "string", "title": "Job Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/search": {"get": {"tags": ["quizzes"], "summary": "Search Quizzes", "description": "Hybrid semantic + keyword search across quiz titles and questions.", "operationId": "search_quizzes_api_v1_quizzes_search_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "q", "in": "query", "required": true, "schema": {"type": "string", "minLength": 2, "maxLength": 200, "title": "Q"}}, {"name": "mode", "in": "query", "required": false, "schema": {"type": "string", "default": "all", "title": "Mode"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/sessions": {"get": {"tags": ["quizzes"], "summary": "List Quiz Sessions", "description": "One management row per accessible quiz: attempt state, progress and last score.\n\nPowers the session-management view (start / resume / review + analysis,\nrepeat, rename, delete) without the client fanning out per-quiz requests.", "operationId": "list_quiz_sessions_api_v1_quizzes_sessions_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/quizzes/share-policy": {"get": {"tags": ["quizzes"], "summary": "Share Policy", "description": "Whether this site lets a learner hand a session to somebody.\n\nAsked before the share dialog offers to make a link, so a switch an\nadministrator has thrown reads as \"not offered\" rather than as a button\nthat fails when pressed.", "operationId": "share_policy_api_v1_quizzes_share_policy_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/quizzes/{quiz_id}": {"get": {"tags": ["quizzes"], "summary": "Get Quiz", "description": "Return selected questions; only an authorized study/completed attempt can reveal answers.", "operationId": "get_quiz_api_v1_quizzes__quiz_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}, {"name": "study", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Study"}}, {"name": "attempt_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Attempt Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "patch": {"tags": ["quizzes"], "summary": "Update Quiz", "description": "Update quiz metadata (title, etc.) \u2014 owner or admin only.", "operationId": "update_quiz_api_v1_quizzes__quiz_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QuizUpdate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QuizResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["quizzes"], "summary": "Delete Quiz", "description": "Soft-delete quiz \u2014 moves to trash. Restore via PATCH /{quiz_id}/restore.", "operationId": "delete_quiz_api_v1_quizzes__quiz_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/{quiz_id}/shuffle": {"post": {"tags": ["quizzes"], "summary": "Shuffle Quiz", "description": "Return quiz with question order and optionally option order shuffled. Does not modify DB.", "operationId": "shuffle_quiz_api_v1_quizzes__quiz_id__shuffle_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}, {"name": "shuffle_options", "in": "query", "required": false, "schema": {"type": "boolean", "description": "Also shuffle answer options within each question", "default": true, "title": "Shuffle Options"}, "description": "Also shuffle answer options within each question"}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QuizDetail"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/{quiz_id}/review": {"get": {"tags": ["quizzes"], "summary": "Review Quiz", "description": "Get quiz with answers \u2014 only if user has completed an attempt.", "operationId": "review_quiz_api_v1_quizzes__quiz_id__review_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QuizReview"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/{quiz_id}/questions": {"get": {"tags": ["quizzes"], "summary": "Get Quiz Questions For Edit", "description": "Get all questions with answers for editing \u2014 moderator/admin only.", "operationId": "get_quiz_questions_for_edit_api_v1_quizzes__quiz_id__questions_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/{quiz_id}/questions/{question_id}": {"patch": {"tags": ["quizzes"], "summary": "Update Question", "description": "Update a quiz question \u2014 change is shared: affects all quizzes using this question.", "operationId": "update_question_api_v1_quizzes__quiz_id__questions__question_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}, {"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "title": "Data"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["quizzes"], "summary": "Delete Question", "description": "Remove a question from this quiz. If it's shared, only removes the link (question stays in bank).\nIf it's only in this quiz, deletes it from the bank too.", "operationId": "delete_question_api_v1_quizzes__quiz_id__questions__question_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}, {"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/{quiz_id}/publish": {"patch": {"tags": ["quizzes"], "summary": "Set Quiz Published", "description": "Hide (published=false) or show (published=true) a quiz for regular users.", "operationId": "set_quiz_published_api_v1_quizzes__quiz_id__publish_patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}, {"name": "published", "in": "query", "required": false, "schema": {"type": "boolean", "default": true, "title": "Published"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/trash": {"get": {"tags": ["quizzes"], "summary": "List Trash", "description": "List soft-deleted quizzes \u2014 admin/moderator only.", "operationId": "list_trash_api_v1_quizzes_trash_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/QuizResponse"}, "type": "array", "title": "Response List Trash Api V1 Quizzes Trash Get"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/quizzes/{quiz_id}/restore": {"patch": {"tags": ["quizzes"], "summary": "Restore Quiz", "description": "Restore a soft-deleted quiz from trash.", "operationId": "restore_quiz_api_v1_quizzes__quiz_id__restore_patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QuizResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/{quiz_id}/permanent": {"delete": {"tags": ["quizzes"], "summary": "Permanently Delete Quiz", "description": "Permanently delete a quiz that's already in trash. Questions stay in bank.", "operationId": "permanently_delete_quiz_api_v1_quizzes__quiz_id__permanent_delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/{quiz_id}/share": {"patch": {"tags": ["quizzes"], "summary": "Share Quiz", "operationId": "share_quiz_api_v1_quizzes__quiz_id__share_patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}, {"name": "shared", "in": "query", "required": true, "schema": {"type": "boolean", "title": "Shared"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/quizzes/{quiz_id}/share-link": {"post": {"tags": ["quizzes"], "summary": "Create Share Link", "description": "Mark a general quiz shareable and return its public token.", "operationId": "create_share_link_api_v1_quizzes__quiz_id__share_link_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["quizzes"], "summary": "Revoke Share Link", "operationId": "revoke_share_link_api_v1_quizzes__quiz_id__share_link_delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/attempts/start": {"post": {"tags": ["attempts"], "summary": "Start Attempt", "operationId": "start_attempt_api_v1_attempts_start_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "query", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}, {"name": "fresh", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Fresh"}}, {"name": "mode", "in": "query", "required": false, "schema": {"anyOf": [{"enum": ["study", "exam"], "type": "string"}, {"type": "null"}], "title": "Mode"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AttemptResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/attempts/{attempt_id}/answer": {"post": {"tags": ["attempts"], "summary": "Record Answer", "description": "Record one answer, the moment it is given.\n\nThis is the durable write. In study it counts immediately; in an exam it is\nkept as a draft that no score, analysis or recommendation can see until the\npaper is handed in. Either way it survives the tab closing, the laptop\ndying and the learner coming back a month later, which is what a saved\nsession ought to mean.\n\nAnswering again replaces the answer rather than adding one \u2014 latest pick\nwins \u2014 so the player can call this on every change without bookkeeping.", "operationId": "record_answer_api_v1_attempts__attempt_id__answer_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "attempt_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Attempt Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AnswerRecord"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/attempts/{attempt_id}/answer/{question_id}": {"delete": {"tags": ["attempts"], "summary": "Clear Answer", "description": "Take back an answer, so the question is unanswered again.\n\nThe player calls this when somebody clears a question \u2014 a misclick, or a\nchoice they want to make again. Without it the record would keep the answer\nthey withdrew, and in study, where answers count as they are given, a\nwithdrawn answer would go on counting.", "operationId": "clear_answer_api_v1_attempts__attempt_id__answer__question_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "attempt_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Attempt Id"}}, {"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/attempts/{attempt_id}/submit": {"post": {"tags": ["attempts"], "summary": "Submit Attempt", "operationId": "submit_attempt_api_v1_attempts__attempt_id__submit_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "attempt_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Attempt Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AttemptSubmit"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AttemptDetail"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/attempts/": {"get": {"tags": ["attempts"], "summary": "List Attempts", "operationId": "list_attempts_api_v1_attempts__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Quiz Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/AttemptResponse"}, "title": "Response List Attempts Api V1 Attempts  Get"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/attempts/progress": {"post": {"tags": ["attempts"], "summary": "Save Progress", "description": "Save in-progress quiz answers to Redis (survives logout/browser change).\nEach attempt gets its own saved progress (key includes attempt_id).\nAlso records the latest active browser session for diagnostics. Resuming\nfrom another browser is allowed; the newest browser takes over the attempt.", "operationId": "save_progress_api_v1_attempts_progress_post", "security": [{"OAuth2PasswordBearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProgressSave"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "get": {"tags": ["attempts"], "summary": "Get Progress", "description": "Retrieve in-progress quiz answers from Redis.\nFinds the latest incomplete attempt for this quiz, then checks Redis.\nAllows another browser/device to resume the attempt; the newest browser\ntakes over the soft activity marker instead of blocking with a 409.\nAuto-submits timed quizzes if timer has expired.", "operationId": "get_progress_api_v1_attempts_progress_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "query", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/attempts/progress/{attempt_id}": {"delete": {"tags": ["attempts"], "summary": "Clear Progress", "description": "Clear saved progress when quiz is submitted.", "operationId": "clear_progress_api_v1_attempts_progress__attempt_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "attempt_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Attempt Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/attempts/reset-all": {"post": {"tags": ["attempts"], "summary": "Reset All Practice Data", "description": "Remove everything this learner has practised, and nothing they have made.\n\nGoes: their own sessions and the attempts and answers inside them, saved\nin-progress state, card scheduling and review history, study-plan progress\nand reading marks, saved questions, and the notes written on questions and\non article sections. Stays: the account, the bank (every shared test,\nquestion, article and plan), AI conversations.\n\nThe sessions themselves used to stay, which is not what reset means. A\nlearner who cleared their data found the same list of sessions waiting,\neach one back to zero \u2014 the user's words: \"my sessions were still there,\njust with no progress.\" A session is a sitting; clearing the sittings and\nleaving their shells is a half-reset that looks like a bug because it is\none. Only unshared sessions go: a test somebody built and shared is bank\ncontent and belongs to the bank, not to the person who happened to make it.\n\nReturns the counts so the page can say what went.", "operationId": "reset_all_practice_data_api_v1_attempts_reset_all_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResetConfirm"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/attempts/quiz/{quiz_id}/in-progress": {"get": {"tags": ["attempts"], "summary": "Get In Progress Attempt", "description": "Return the latest incomplete attempt for a quiz, or null.", "operationId": "get_in_progress_attempt_api_v1_attempts_quiz__quiz_id__in_progress_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/attempts/in-progress": {"get": {"tags": ["attempts"], "summary": "Get In Progress Attempts", "description": "Return all incomplete (not yet submitted) attempts for this user.", "operationId": "get_in_progress_attempts_api_v1_attempts_in_progress_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/attempts/history": {"get": {"tags": ["attempts"], "summary": "Get Quiz History", "description": "Return per-quiz attempt history for the performance line graph (expired attempts excluded).", "operationId": "get_quiz_history_api_v1_attempts_history_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/attempts/stats/dashboard": {"get": {"tags": ["attempts"], "summary": "Get Dashboard Stats", "operationId": "get_dashboard_stats_api_v1_attempts_stats_dashboard_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DashboardStats"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/attempts/{attempt_id}": {"get": {"tags": ["attempts"], "summary": "Get Attempt", "operationId": "get_attempt_api_v1_attempts__attempt_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "attempt_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Attempt Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AttemptDetail"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/attempts/quiz/{quiz_id}/analysis": {"get": {"tags": ["attempts"], "summary": "Quiz Analysis", "description": "The analysis for a session addressed by quiz rather than by attempt.\n\nA session nobody has sat still has an analysis: it is all zeroes and every\nquestion is skipped, and that is the honest picture \u2014 the same page the\nlearner will see filled in, showing what is missing. Answering it with\n\"nothing to see\" made a session that had never been opened look like a\ndifferent kind of object from one that had.\n\nWhere an attempt exists, its analysis is returned instead, so the page is\nthe same whichever way it was reached.", "operationId": "quiz_analysis_api_v1_attempts_quiz__quiz_id__analysis_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/attempts/{attempt_id}/recommendations": {"get": {"tags": ["attempts"], "summary": "Attempt Recommendations", "description": "This session's weakest topics, asked three ways.\n\nWhich reading to go back to, which discipline is weak, which organ system\nis weak \u2014 the same rules the Analysis page uses, from the same place, so\nthe two cannot disagree about where a question belongs.\n\nA session still running is not marked, so there is nothing to rank: an\nexam would be answering the question it is asking.", "operationId": "attempt_recommendations_api_v1_attempts__attempt_id__recommendations_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "attempt_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Attempt Id"}}, {"name": "group", "in": "query", "required": false, "schema": {"enum": ["articles", "disciplines", "systems"], "type": "string", "default": "disciplines", "title": "Group"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/attempts/{attempt_id}/analysis": {"get": {"tags": ["attempts"], "summary": "Attempt Analysis", "description": "Everything the session analysis shows, in one call.\n\nThe results page had a score and a list of explanations; what a learner\nneeds afterwards is where the time went and which topics to go back to.\nPeer statistics come from every other completed answer to the same question,\nwhich is the only comparison available and an honest one.", "operationId": "attempt_analysis_api_v1_attempts__attempt_id__analysis_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "attempt_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Attempt Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/admin/users": {"get": {"tags": ["admin"], "summary": "List Users", "operationId": "list_users_api_v1_admin_users_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/UserResponse"}, "type": "array", "title": "Response List Users Api V1 Admin Users Get"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/admin/users/{user_id}/role": {"put": {"tags": ["admin"], "summary": "Update User Role", "operationId": "update_user_role_api_v1_admin_users__user_id__role_put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "user_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "User Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserUpdateRole"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/admin/users/{user_id}/unthrottle": {"put": {"tags": ["admin"], "summary": "Set User Unthrottle", "description": "Set or clear the unthrottle flag for a user \u2014 exempt from AI/TTS rate limits.", "operationId": "set_user_unthrottle_api_v1_admin_users__user_id__unthrottle_put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "user_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "User Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true, "title": "Data"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/admin/users/{user_id}": {"delete": {"tags": ["admin"], "summary": "Delete User", "description": "Delete a user and all their data (attempts, favorites, settings).", "operationId": "delete_user_api_v1_admin_users__user_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "user_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "User Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/admin/models/available": {"get": {"tags": ["admin"], "summary": "List Available Models", "description": "Returns active models for a given task \u2014 for users to choose when taking/creating a quiz.", "operationId": "list_available_models_api_v1_admin_models_available_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "task", "in": "query", "required": false, "schema": {"type": "string", "default": "extraction", "title": "Task"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/admin/litellm/models": {"post": {"tags": ["admin"], "summary": "Search Litellm Models", "description": "Query available models from LiteLLM proxy or OpenAI-compatible API.", "operationId": "search_litellm_models_api_v1_admin_litellm_models_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LiteLLMSearchRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/admin/models": {"get": {"tags": ["admin"], "summary": "List Models", "operationId": "list_models_api_v1_admin_models_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/AIModelConfigResponse"}, "type": "array", "title": "Response List Models Api V1 Admin Models Get"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "post": {"tags": ["admin"], "summary": "Create Model", "operationId": "create_model_api_v1_admin_models_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AIModelConfigCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AIModelConfigResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/admin/models/{model_id}": {"put": {"tags": ["admin"], "summary": "Update Model", "operationId": "update_model_api_v1_admin_models__model_id__put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "model_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Model Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AIModelConfigUpdate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AIModelConfigResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["admin"], "summary": "Delete Model", "operationId": "delete_model_api_v1_admin_models__model_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "model_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Model Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/admin/models/{model_id}/test": {"post": {"tags": ["admin"], "summary": "Test Model", "description": "Exercise a model the way its job will.\n\nA chat model answers a prompt, a voice speaks, a transcriber listens, and\nthe tool model reads a word off a picture. A test that only proved the id\nwas spelled correctly taught administrators to distrust the button.", "operationId": "test_model_api_v1_admin_models__model_id__test_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "model_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Model Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/admin/tts/voices": {"post": {"tags": ["admin"], "summary": "Search Tts Voices", "description": "Discover local TTS voices/models from LiteLLM or the local speech gateway.", "operationId": "search_tts_voices_api_v1_admin_tts_voices_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TTSVoiceSearchRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/admin/settings": {"get": {"tags": ["admin"], "summary": "Get Settings", "description": "Get system settings.", "operationId": "get_settings_api_v1_admin_settings_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "put": {"tags": ["admin"], "summary": "Update Settings", "description": "Update system settings.", "operationId": "update_settings_api_v1_admin_settings_put", "requestBody": {"content": {"application/json": {"schema": {"additionalProperties": true, "type": "object", "title": "Settings Data"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/admin/embedding/test": {"post": {"tags": ["admin"], "summary": "Test Embedding", "description": "Test the currently configured embedding model.", "operationId": "test_embedding_api_v1_admin_embedding_test_post", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/admin/rerank/test": {"post": {"tags": ["admin"], "summary": "Test Rerank", "description": "Check that the configured reranker answers, and that it answers sensibly.\n\nA reranker that returns 200 and ranks the decoy first is worse than one that\nis switched off, and nothing else on the site would ever tell you: its whole\noutput is an order somebody has to already know the right answer to judge.", "operationId": "test_rerank_api_v1_admin_rerank_test_post", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/admin/classification-snapshots": {"get": {"tags": ["admin"], "summary": "List Classification Snapshots", "description": "List recent classification rollback snapshots.", "operationId": "list_classification_snapshots_api_v1_admin_classification_snapshots_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 50, "minimum": 1, "default": 10, "title": "Limit"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/admin/classification-snapshots/{snapshot_id}/rollback": {"post": {"tags": ["admin"], "summary": "Rollback Classification Snapshot", "description": "Restore question tag assignments from a saved snapshot.", "operationId": "rollback_classification_snapshot_api_v1_admin_classification_snapshots__snapshot_id__rollback_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "snapshot_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Snapshot Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/admin/embedding/health": {"get": {"tags": ["admin"], "summary": "Embedding Health", "description": "How much of the bank is semantically searchable under the active model.\n\nVectors from two different embedding models are not comparable, so a model\nchange has to be visible rather than silently degrading search quality.", "operationId": "embedding_health_api_v1_admin_embedding_health_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/admin/embedding/regenerate": {"post": {"tags": ["admin"], "summary": "Regenerate Embeddings", "description": "Queue a background task to re-embed questions with the current model.", "operationId": "regenerate_embeddings_api_v1_admin_embedding_regenerate_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "stale_only", "in": "query", "required": false, "schema": {"type": "boolean", "description": "Only rows with no vector or a vector from another model", "default": true, "title": "Stale Only"}, "description": "Only rows with no vector or a vector from another model"}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/tts/voices": {"get": {"tags": ["tts"], "summary": "Get Voices", "description": "Every active voice, each saying which model serves it.\n\nAll of them, not only the locally served ones: a learner is offered every\nvoice an administrator added, whichever model it belongs to.", "operationId": "get_voices_api_v1_tts_voices_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/tts/speak": {"post": {"tags": ["tts"], "summary": "Text To Speech", "description": "Convert text to speech using configured or user-selected TTS model.", "operationId": "text_to_speech_api_v1_tts_speak_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TTSRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/tts/transcribe": {"post": {"tags": ["tts"], "summary": "Speech To Text", "description": "Transcribe microphone audio using the configured STT model.", "operationId": "speech_to_text_api_v1_tts_transcribe_post", "requestBody": {"content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_speech_to_text_api_v1_tts_transcribe_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/categories/": {"get": {"tags": ["categories"], "summary": "List Categories", "operationId": "list_categories_api_v1_categories__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/CategoryResponse"}, "type": "array", "title": "Response List Categories Api V1 Categories  Get"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "post": {"tags": ["categories"], "summary": "Create Category", "operationId": "create_category_api_v1_categories__post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CategoryCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CategoryResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/categories/{category_id}": {"delete": {"tags": ["categories"], "summary": "Delete Category", "operationId": "delete_category_api_v1_categories__category_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "category_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Category Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/categories/quizzes/{quiz_id}": {"patch": {"tags": ["categories"], "summary": "Set Quiz Category", "description": "Assign or remove a category from a quiz. Pass category_id=null to uncategorize.", "operationId": "set_quiz_category_api_v1_categories_quizzes__quiz_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "quiz_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Quiz Id"}}, {"name": "category_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/{question_id}": {"delete": {"tags": ["questions"], "summary": "Delete Question", "description": "Move a question to the trash. Restore with PATCH /{id}/restore.", "operationId": "delete_question_api_v1_questions__question_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "patch": {"tags": ["questions"], "summary": "Edit Question", "description": "Edit a question \u2014 moderators, or an educator granted its category.", "operationId": "edit_question_api_v1_questions__question_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QuestionEdit"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/trash": {"get": {"tags": ["questions"], "summary": "List Trashed Questions", "description": "What this person has deleted and could still put back.\n\nA moderator sees everything deleted; anyone else sees their own, and an\neducator sees what falls inside the categories they were granted.", "operationId": "list_trashed_questions_api_v1_questions_trash_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 100, "title": "Limit"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/{question_id}/restore": {"patch": {"tags": ["questions"], "summary": "Restore Question", "description": "Put a question back. Its id never changed, so everything that pointed at\nit \u2014 attempts, quizzes, exams, media \u2014 still does.", "operationId": "restore_question_api_v1_questions__question_id__restore_patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/{question_id}/permanent": {"delete": {"tags": ["questions"], "summary": "Delete Question Permanently", "description": "Erase it. This takes its answers, its quiz membership and its media with\nit, and no restore can bring any of that back \u2014 so only a moderator may,\nand only something already in the trash.", "operationId": "delete_question_permanently_api_v1_questions__question_id__permanent_delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/{question_id}/category": {"patch": {"tags": ["questions"], "summary": "Set Question Category", "description": "Assign or remove a question category.", "operationId": "set_question_category_api_v1_questions__question_id__category_patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}, {"name": "category_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/bank/ids": {"get": {"tags": ["questions"], "summary": "Get Bank Ids", "description": "Return just IDs for all matching questions (for server-side select-all).", "operationId": "get_bank_ids_api_v1_questions_bank_ids_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "q", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Q"}}, {"name": "quiz_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Quiz Id"}}, {"name": "category_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}}, {"name": "category_ids", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Comma-separated category IDs (OR filter)", "title": "Category Ids"}, "description": "Comma-separated category IDs (OR filter)"}, {"name": "uncategorized", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Uncategorized"}}, {"name": "favorites_only", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Favorites Only"}}, {"name": "folder_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "Only the questions in this folder", "title": "Folder Id"}, "description": "Only the questions in this folder"}, {"name": "editable_only", "in": "query", "required": false, "schema": {"type": "boolean", "description": "Only questions the caller may edit. What the question manager asks for: a grantee was shown the whole bank with an Edit and a Delete on every row, beside a count that said 214.", "default": false, "title": "Editable Only"}, "description": "Only questions the caller may edit. What the question manager asks for: a grantee was shown the whole bank with an Edit and a Delete on every row, beside a count that said 214."}, {"name": "tag_ids", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Comma-separated tag IDs (AND filter)", "title": "Tag Ids"}, "description": "Comma-separated tag IDs (AND filter)"}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/bank": {"get": {"tags": ["questions"], "summary": "Get Question Bank", "description": "List all questions across all quizzes. Supports keyword filter and quiz filter.", "operationId": "get_question_bank_api_v1_questions_bank_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "q", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Q"}}, {"name": "quiz_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Quiz Id"}}, {"name": "category_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}}, {"name": "category_ids", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Comma-separated category IDs (OR filter)", "title": "Category Ids"}, "description": "Comma-separated category IDs (OR filter)"}, {"name": "uncategorized", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Uncategorized"}}, {"name": "needs", "in": "query", "required": false, "schema": {"anyOf": [{"enum": ["category", "explanation", "difficulty", "private"], "type": "string"}, {"type": "null"}], "description": "Editorial gap filter used by the question manager", "title": "Needs"}, "description": "Editorial gap filter used by the question manager"}, {"name": "favorites_only", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Favorites Only"}}, {"name": "difficulty", "in": "query", "required": false, "schema": {"anyOf": [{"enum": ["easy", "medium", "hard"], "type": "string"}, {"type": "null"}], "title": "Difficulty"}}, {"name": "article_ids", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Comma-separated article IDs (OR filter)", "title": "Article Ids"}, "description": "Comma-separated article IDs (OR filter)"}, {"name": "folder_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "Only the questions in this folder", "title": "Folder Id"}, "description": "Only the questions in this folder"}, {"name": "tag_ids", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Comma-separated tag IDs (AND filter)", "title": "Tag Ids"}, "description": "Comma-separated tag IDs (AND filter)"}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 200, "default": 50, "title": "Limit"}}, {"name": "offset", "in": "query", "required": false, "schema": {"type": "integer", "default": 0, "title": "Offset"}}, {"name": "editable_only", "in": "query", "required": false, "schema": {"type": "boolean", "description": "Only questions the caller may edit. What the question manager asks for: a grantee was shown the whole bank with an Edit and a Delete on every row, beside a count that said 214.", "default": false, "title": "Editable Only"}, "description": "Only questions the caller may edit. What the question manager asks for: a grantee was shown the whole bank with an Edit and a Delete on every row, beside a count that said 214."}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/create": {"post": {"tags": ["questions"], "summary": "Create Question Manually", "description": "Create a single question manually. Moderators.\n\nDaniel's rule, stated twice: a scoped grant is edit rights, no creation\nand no deletion. I argued the other side once \u2014 scope the picker and the\nchoice becomes answerable \u2014 and he was clear that the answer is to not\noffer it rather than to offer it narrowed. The page hides the button to\nmatch, so nobody meets a control that refuses.\n\n`assert_can_manage_category` below still holds the branch line for the\npaths that do create: it is the check, not the role, that decides where\nsomething may be filed.", "operationId": "create_question_manually_api_v1_questions_create_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ManualQuestionCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/questions/upload-image": {"post": {"tags": ["questions"], "summary": "Upload Question Image", "description": "A figure for a question, from somebody who may edit questions.\n\nThis took a signed-in user, believed `content_type` and the filename's\nextension, read the whole file however large it was, and wrote it to disk \u2014\nso any account could fill the volume with anything at all, and an `.svg`\nreached a reader's browser from this origin without being looked at. Every\nother door in this app sniffs the bytes; an extension is a claim, never the\ndecision.\n\nGuarded like the rest of question editing rather than by `require_moderator`:\nsomebody with a grant over a branch of the taxonomy edits questions in it,\nand adding a figure is part of that.", "operationId": "upload_question_image_api_v1_questions_upload_image_post", "requestBody": {"content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_upload_question_image_api_v1_questions_upload_image_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/questions/images": {"get": {"tags": ["questions"], "summary": "List Question Images", "description": "List all unique question images for the image bank browser.", "operationId": "list_question_images_api_v1_questions_images_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/questions/builder/count": {"get": {"tags": ["questions"], "summary": "Count Builder Questions", "operationId": "count_builder_questions_api_v1_questions_builder_count_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "category_ids", "in": "query", "required": false, "schema": {"type": "array", "items": {"type": "integer"}, "default": [], "title": "Category Ids"}}, {"name": "state", "in": "query", "required": false, "schema": {"enum": ["all", "unused", "incorrect", "bookmarked"], "type": "string", "default": "all", "title": "State"}}, {"name": "difficulty", "in": "query", "required": false, "schema": {"anyOf": [{"enum": ["easy", "medium", "hard"], "type": "string"}, {"type": "null"}], "title": "Difficulty"}}, {"name": "article_ids", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Comma-separated article IDs (OR filter)", "title": "Article Ids"}, "description": "Comma-separated article IDs (OR filter)"}, {"name": "tag_ids", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Comma-separated tag IDs (AND filter)", "title": "Tag Ids"}, "description": "Comma-separated tag IDs (AND filter)"}, {"name": "system_ids", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Comma-separated organ system IDs (OR filter)", "title": "System Ids"}, "description": "Comma-separated organ system IDs (OR filter)"}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/builder": {"post": {"tags": ["questions"], "summary": "Create Builder Quiz", "operationId": "create_builder_quiz_api_v1_questions_builder_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/GenerateTestRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/questions/builder/prepared": {"get": {"tags": ["questions"], "summary": "Preview Prepared Session", "description": "The session this learner would be given, before anything is written.\n\nReturned without the question ids: the plan is for reading, and handing the\nstems' identifiers to a page that only draws a summary is how a preview\nbecomes a way to enumerate the bank.", "operationId": "preview_prepared_session_api_v1_questions_builder_prepared_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "count", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer", "maximum": 200, "minimum": 1}, {"type": "null"}], "title": "Count"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "post": {"tags": ["questions"], "summary": "Start Prepared Session", "description": "Commit a prepared session, and return the plan it was actually built from.\n\nThe plan is recomputed here rather than carried from the preview, and the\ntest is built from that plan's own question ids \u2014 so the account handed\nback describes this session by construction, not by the two calls happening\nto agree. A learner who changed the length gets the plan for the length\nthey chose, which is the honest answer and not the one they were shown.", "operationId": "start_prepared_session_api_v1_questions_builder_prepared_post", "security": [{"OAuth2PasswordBearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PreparedSessionRequest"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/builder/describe": {"post": {"tags": ["questions"], "summary": "Build Test From Description", "description": "Turn \"what I want to study\" into a test of matching bank questions.", "operationId": "build_test_from_description_api_v1_questions_builder_describe_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DescribeRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/questions/builder/from-upload": {"post": {"tags": ["questions"], "summary": "Build Test From Upload", "description": "Match an uploaded document against the bank and build a test from it.\n\nThe document is read in memory and never stored: it is a search query, not\na source of new questions, so there is nothing to keep or delete later.", "operationId": "build_test_from_upload_api_v1_questions_builder_from_upload_post", "requestBody": {"content": {"multipart/form-data": {"schema": {"$ref": "#/components/schemas/Body_build_test_from_upload_api_v1_questions_builder_from_upload_post"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/questions/from-bank": {"post": {"tags": ["questions"], "summary": "Create Quiz From Bank", "operationId": "create_quiz_from_bank_api_v1_questions_from_bank_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateFromBankRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/questions/bulk-category": {"post": {"tags": ["questions"], "summary": "Bulk Set Question Category", "description": "Assign or remove a category from multiple questions.", "operationId": "bulk_set_question_category_api_v1_questions_bulk_category_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/BulkCategoryRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/questions/bulk": {"post": {"tags": ["questions"], "summary": "Bulk Question Action", "description": "Apply one editorial action to up to 500 checked questions.", "operationId": "bulk_question_action_api_v1_questions_bulk_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/BulkQuestionAction"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/questions/detail/{question_id}/note": {"get": {"tags": ["questions"], "summary": "Read Question Note", "description": "This learner's own note on this question.", "operationId": "read_question_note_api_v1_questions_detail__question_id__note_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "put": {"tags": ["questions"], "summary": "Write Question Note", "description": "Save or clear it. Empty means delete, so an emptied note leaves no trace.", "operationId": "write_question_note_api_v1_questions_detail__question_id__note_put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QuestionNoteIn"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/detail/{question_id}/versions": {"get": {"tags": ["questions"], "summary": "List Question Versions", "description": "Every snapshot of a question, newest first.", "operationId": "list_question_versions_api_v1_questions_detail__question_id__versions_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/detail/{question_id}/versions/{version_id}/restore": {"post": {"tags": ["questions"], "summary": "Restore Question Version", "description": "Roll a question back, snapshotting the current state first so it is undoable too.", "operationId": "restore_question_version_api_v1_questions_detail__question_id__versions__version_id__restore_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}, {"name": "version_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Version Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/detail/{question_id}": {"get": {"tags": ["questions"], "summary": "Get Question Detail", "description": "One question, for the full-page editor.\n\nPath is /detail/{id} rather than /{id} so it cannot shadow the static\nroutes above it as more are added.", "operationId": "get_question_detail_api_v1_questions_detail__question_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/manage/summary": {"get": {"tags": ["questions"], "summary": "Question Manager Summary", "description": "Editorial health counters shown as one-click filters in the question manager.\n\nA granted educator sees the counts for their own categories only.", "operationId": "question_manager_summary_api_v1_questions_manage_summary_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/questions/export/qti": {"get": {"tags": ["questions"], "summary": "Export Qti", "description": "Export questions as QTI 2.1 XML.", "operationId": "export_qti_api_v1_questions_export_qti_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_ids", "in": "query", "required": false, "schema": {"type": "string", "description": "Comma-separated question IDs (omit for all shared)", "title": "Question Ids"}, "description": "Comma-separated question IDs (omit for all shared)"}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/export/csv": {"get": {"tags": ["questions"], "summary": "Export Csv", "description": "Export questions as CSV \u2014 the same columns `POST /drafts/import` reads.\n\nDeliberately round-trippable. An export that cannot be imported again is a\nprintout, and the reason anybody asks for one is to move a bank.", "operationId": "export_csv_api_v1_questions_export_csv_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_ids", "in": "query", "required": false, "schema": {"type": "string", "description": "Comma-separated question IDs (omit for all shared)", "title": "Question Ids"}, "description": "Comma-separated question IDs (omit for all shared)"}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/detail/{question_id}/figures": {"get": {"tags": ["questions"], "summary": "List Figures", "description": "The figures on a question \u2014 the answer-side ones only for its editors.\n\nThis had no check at all. Anybody signed in could ask for any question's\nfigures by id and be handed its explanation images: the paths, and the\nlibrary record that rides on them, whose titles are things like \"Neonatal\nHerpes Simplex \u00b7 Q874\". No attempt required, and the answer in the title.\nThe same rule as the detail route it sits beside.", "operationId": "list_figures_api_v1_questions_detail__question_id__figures_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "post": {"tags": ["questions"], "summary": "Add Figure", "operationId": "add_figure_api_v1_questions_detail__question_id__figures_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/app__routers__questions__FigureIn"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/figures/{figure_id}": {"patch": {"tags": ["questions"], "summary": "Update Figure", "operationId": "update_figure_api_v1_questions_figures__figure_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "figure_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Figure Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FigureUpdate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["questions"], "summary": "Remove Figure", "description": "Take a figure off a question. The image itself stays in the bank.", "operationId": "remove_figure_api_v1_questions_figures__figure_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "figure_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Figure Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/questions/{question_id}/articles": {"get": {"tags": ["questions"], "summary": "Question Articles", "description": "The reading linked to one question: the mirror of `GET /articles/{id}/questions`.\n\nBoth sides answer with the same fields for the link itself \u2014 `section_id`\nand `section_title` \u2014 so the relationship reads the same whichever end you\nare standing at. Only cards are returned, never article bodies: this used\nto be `GET /articles/linked`, which sent the whole prose of every linked\narticle to a page that draws a list of titles.", "operationId": "question_articles_api_v1_questions__question_id__articles_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/question-categories/grants": {"get": {"tags": ["question-categories"], "summary": "List Category Grants", "description": "Every per-category editorial grant, for the question-manager admin panel.", "operationId": "list_category_grants_api_v1_question_categories_grants_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/question-categories/grantable-users": {"get": {"tags": ["question-categories"], "summary": "Grantable Users", "description": "Users a grant can be given to \u2014 moderators already manage everything.", "operationId": "grantable_users_api_v1_question_categories_grantable_users_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/question-categories/{cat_id}/grants": {"post": {"tags": ["question-categories"], "summary": "Add Category Grant", "description": "Let one user edit the questions in this category and everything under it.", "operationId": "add_category_grant_api_v1_question_categories__cat_id__grants_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "cat_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Cat Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/GrantIn"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/question-categories/{cat_id}/grants/{user_id}": {"delete": {"tags": ["question-categories"], "summary": "Remove Category Grant", "operationId": "remove_category_grant_api_v1_question_categories__cat_id__grants__user_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "cat_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Cat Id"}}, {"name": "user_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "User Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/question-categories/my-grants": {"get": {"tags": ["question-categories"], "summary": "My Category Grants", "description": "What the signed-in user may edit \u2014 drives the question-manager entry point.", "operationId": "my_category_grants_api_v1_question_categories_my_grants_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/question-categories/": {"get": {"tags": ["question-categories"], "summary": "List Question Categories", "operationId": "list_question_categories_api_v1_question_categories__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/QCatResponse"}, "type": "array", "title": "Response List Question Categories Api V1 Question Categories  Get"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "post": {"tags": ["question-categories"], "summary": "Create Question Category", "operationId": "create_question_category_api_v1_question_categories__post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/QCatCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QCatResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/question-categories/{cat_id}": {"patch": {"tags": ["question-categories"], "summary": "Update Question Category", "operationId": "update_question_category_api_v1_question_categories__cat_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "cat_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Cat Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QCatCreate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QCatResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["question-categories"], "summary": "Delete Question Category", "operationId": "delete_question_category_api_v1_question_categories__cat_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "cat_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Cat Id"}}, {"name": "move_to", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Move To"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/question-categories/{cat_id}/create-quiz": {"post": {"tags": ["question-categories"], "summary": "Create Quiz From Question Category", "operationId": "create_quiz_from_question_category_api_v1_question_categories__cat_id__create_quiz_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "cat_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Cat Id"}}, {"name": "title", "in": "query", "required": true, "schema": {"type": "string", "title": "Title"}}, {"name": "mode", "in": "query", "required": false, "schema": {"type": "string", "default": "timed", "title": "Mode"}}, {"name": "time_limit_minutes", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Time Limit Minutes"}}, {"name": "count", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Count"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/favorites": {"get": {"tags": ["favorites"], "summary": "List Favorites", "description": "Get list of favorited question IDs for current user.", "operationId": "list_favorites_api_v1_favorites_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"type": "integer"}, "type": "array", "title": "Response List Favorites Api V1 Favorites Get"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "post": {"tags": ["favorites"], "summary": "Add Favorite", "description": "Add a question to user's favorites.", "operationId": "add_favorite_api_v1_favorites_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/FavoriteCreate"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FavoriteResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/favorites/{question_id}": {"delete": {"tags": ["favorites"], "summary": "Remove Favorite", "description": "Remove a question from user's favorites.", "operationId": "remove_favorite_api_v1_favorites__question_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/teach/models": {"get": {"tags": ["teach"], "summary": "List Teach Models", "description": "Return available teach models for the frontend to display.", "operationId": "list_teach_models_api_v1_teach_models_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/teach/policy": {"get": {"tags": ["teach"], "summary": "Tutor Policy", "description": "Whether the tutor may be opened during a session.\n\nAsked before the quiz offers it, so a switch an administrator has thrown\nreads as \"not there\" rather than as a button that fails when pressed.\nStudy mode is a separate rule the interface already applies and the server\nenforces regardless of this answer.", "operationId": "tutor_policy_api_v1_teach_policy_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/teach/prompt": {"get": {"tags": ["teach"], "summary": "Show Prompt", "description": "What the tutor is actually told, for anyone who maintains questions.\n\nAn educator answering \"why did the tutor say that?\" should be able to read\nthe instructions rather than infer them, and the answer matters: the tutor\nis handed the correct answer and the explanation, and is told it may\nreveal them. That is the reason it is never offered during a running exam.\n\nRendered against a stand-in question so the shape is visible without\nnaming any real one.", "operationId": "show_prompt_api_v1_teach_prompt_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/teach/chat": {"post": {"tags": ["teach"], "summary": "Chat", "description": "Send a message to the teach AI with full question context.", "operationId": "chat_api_v1_teach_chat_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChatRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/contact": {"post": {"tags": ["contact"], "summary": "Submit Contact", "description": "Public endpoint \u2014 no auth required.", "operationId": "submit_contact_api_v1_contact_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/contact/submissions": {"get": {"tags": ["contact"], "summary": "List Submissions", "description": "Every message the contact form has taken. Administrators only.\n\nIt was open to the internet: `require_admin` was imported inside the\nfunction body and never used as a dependency, so the import read as\nprotection and was none. Anyone who guessed the path could read every\nsender's name, address and message.", "operationId": "list_submissions_api_v1_contact_submissions_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/contact/submissions/{submission_id}/read": {"put": {"tags": ["contact"], "summary": "Mark Read", "description": "Mark one as read. Administrators only, for the same reason as the list.", "operationId": "mark_read_api_v1_contact_submissions__submission_id__read_put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "submission_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Submission Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/tags": {"get": {"tags": ["tags"], "summary": "Get Tags", "description": "The organ systems, with how many questions reach each one.\n\nA question reaches a system through the topic it is filed under, and\nthrough every topic above that one \u2014 the same rule the analysis groups by,\nso the number here is the number the filter will return.\n\nIt used to come from a symptom keyword the question happened to mention.\nThose keywords are retired \u2014 the topic carries the system now, which took\nthe Systems axis from about half the bank to all of it.\n\nSubjects and diseases are not retired. They were taken down with the\nkeywords on the argument that the topic tree says the same thing; it mostly\ndoes, but they are the vocabulary people had learned to filter by, and\n\"mostly\" is not a reason to remove somebody's tools. They are counted here\nthe way they always were, from the links a question carries directly.", "operationId": "get_tags_api_v1_tags_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/tags/": {"post": {"tags": ["tags"], "summary": "Create Tag", "description": "Add a top-level entry or a child. New tags appear in every picker at once.", "operationId": "create_tag_api_v1_tags__post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TagWrite"}}}, "required": true}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/tags/{tag_id}": {"patch": {"tags": ["tags"], "summary": "Update Tag", "description": "Rename, reorder, or move a tag under a different parent.", "operationId": "update_tag_api_v1_tags__tag_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "tag_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Tag Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TagUpdate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["tags"], "summary": "Delete Tag", "description": "Delete a tag, optionally moving its questions and children onto another.", "operationId": "delete_tag_api_v1_tags__tag_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "tag_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Tag Id"}}, {"name": "move_to", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Move To"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/tags/{tag_id}/questions": {"post": {"tags": ["tags"], "summary": "Add Questions To Tag", "description": "Attach questions to a tag; they show in every picker using it immediately.", "operationId": "add_questions_to_tag_api_v1_tags__tag_id__questions_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "tag_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Tag Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TagQuestions"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/manual": {"post": {"tags": ["flashcards"], "summary": "Create Deck Manually", "description": "An empty deck, to write cards into by hand.\n\nThe escape hatch, not the way in. Cards belong to an article \u2014 text\nsomebody has written, edited and published \u2014 and `POST\n/articles/{id}/ai-cards` is the path that makes them, inheriting the\narticle's category. This is here for the six cards that belong to no\narticle, and it asks for the category the article would have given.\n\nModerators, and anybody holding a deck grant. Not the editorial grant over\na category: decks are granted on their own, like the taxonomy and the\nimage libraries, because a deck is study material somebody sits with\nunsupervised rather than reading they are steered through.\n\nUnshared, like every other new deck: sharing is a second, deliberate act.", "operationId": "create_deck_manually_api_v1_flashcards_manual_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ManualDeckCreate"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/flashcards/": {"get": {"tags": ["flashcards"], "summary": "List Flashcard Decks", "description": "The bank's decks, optionally by category. Educators and grantees.\n\nThere is no \"my decks\" any more \u2014 a deck is not anybody's. A learner\nstudies what has been shared with them, which is the /shared listing.\nSomebody holding a grant over one deck or one category sees exactly the\ndecks that grant reaches, which is the list they can actually act on.", "operationId": "list_flashcard_decks_api_v1_flashcards__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "include_deleted", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Include Deleted"}}, {"name": "category_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/FlashcardDeckResponse"}, "title": "Response List Flashcard Decks Api V1 Flashcards  Get"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/access": {"get": {"tags": ["flashcards"], "summary": "My Deck Access", "description": "What this person may do here, answered once rather than guessed at.\n\nThe page needs it before it draws anything: a contributor holding one\ngranted deck must not be shown a learner's page with no way in, and a\nlearner must not be shown controls that would refuse them.", "operationId": "my_deck_access_api_v1_flashcards_access_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/flashcards/grants": {"get": {"tags": ["flashcards"], "summary": "List Deck Grants", "description": "Every standing grant, for the page that hands them out.", "operationId": "list_deck_grants_api_v1_flashcards_grants_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "post": {"tags": ["flashcards"], "summary": "Create Deck Grant", "description": "Give one person one deck, or one category and everything beneath it.", "operationId": "create_deck_grant_api_v1_flashcards_grants_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DeckGrantIn"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/flashcards/grants/{grant_id}": {"delete": {"tags": ["flashcards"], "summary": "Delete Deck Grant", "operationId": "delete_deck_grant_api_v1_flashcards_grants__grant_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "grant_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Grant Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/trash": {"get": {"tags": ["flashcards"], "summary": "List Trashed Decks", "description": "Decks in the bin. Editors' business, like the decks themselves.", "operationId": "list_trashed_decks_api_v1_flashcards_trash_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/FlashcardDeckResponse"}, "type": "array", "title": "Response List Trashed Decks Api V1 Flashcards Trash Get"}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/flashcards/shared": {"get": {"tags": ["flashcards"], "summary": "List Shared Decks", "description": "List shared decks sorted by average rating (desc), with pagination.", "operationId": "list_shared_decks_api_v1_flashcards_shared_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 100, "default": 20, "title": "Limit"}}, {"name": "offset", "in": "query", "required": false, "schema": {"type": "integer", "default": 0, "title": "Offset"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/{deck_id}": {"get": {"tags": ["flashcards"], "summary": "Get Flashcard Deck", "description": "Get a deck with all its cards. Accessible if owned or shared.", "operationId": "get_flashcard_deck_api_v1_flashcards__deck_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "deck_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Deck Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FlashcardDeckDetail"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["flashcards"], "summary": "Delete Flashcard Deck", "description": "Soft-delete a deck (moves to trash). Use ?permanent=true to permanently delete.", "operationId": "delete_flashcard_deck_api_v1_flashcards__deck_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "deck_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Deck Id"}}, {"name": "permanent", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Permanent"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "patch": {"tags": ["flashcards"], "summary": "Update Deck", "description": "Update deck metadata (title, category). Owner or admin only.", "operationId": "update_deck_api_v1_flashcards__deck_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "deck_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Deck Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FlashcardDeckUpdate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/{deck_id}/study": {"get": {"tags": ["flashcards"], "summary": "Study Deck", "description": "The deck in the order it should be sat, with what is due said out loud.\n\nOrdering happens here rather than in the page because it needs this\nlearner's history, and a page that fetched the whole review log to sort\ntwenty cards would be downloading a year of answers to draw one screen.", "operationId": "study_deck_api_v1_flashcards__deck_id__study_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "deck_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Deck Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/cards/{card_id}/review": {"post": {"tags": ["flashcards"], "summary": "Review Card", "description": "Record how a card went, so the deck can come back at the right time.", "operationId": "review_card_api_v1_flashcards_cards__card_id__review_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "card_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Card Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CardVerdict"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/questions/{question_id}/cards": {"get": {"tags": ["flashcards"], "summary": "Cards For Question", "description": "The cards an educator tied to this question.\n\nThe link is read from the question's end only. A card that listed the\nquestions it belongs to would hand a learner revising the deck the shape of\nthe exam \u2014 and the answer, since a card's back is an answer.", "operationId": "cards_for_question_api_v1_flashcards_questions__question_id__cards_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/{deck_id}/restore": {"post": {"tags": ["flashcards"], "summary": "Restore Flashcard Deck", "description": "Restore a soft-deleted deck from trash.", "operationId": "restore_flashcard_deck_api_v1_flashcards__deck_id__restore_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "deck_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Deck Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/{deck_id}/share": {"put": {"tags": ["flashcards"], "summary": "Toggle Share Deck", "description": "Toggle sharing on a deck. Owner or admin.", "operationId": "toggle_share_deck_api_v1_flashcards__deck_id__share_put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "deck_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Deck Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["flashcards"], "summary": "Admin Unshare Deck", "description": "Admin: force-unshare any deck.", "operationId": "admin_unshare_deck_api_v1_flashcards__deck_id__share_delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "deck_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Deck Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/{deck_id}/rate": {"post": {"tags": ["flashcards"], "summary": "Rate Deck", "description": "Rate a shared deck (1-5). Updates if already rated.", "operationId": "rate_deck_api_v1_flashcards__deck_id__rate_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "deck_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Deck Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RatingRequest"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/cards/browse": {"get": {"tags": ["flashcards"], "summary": "Browse Flashcards", "description": "Browse the cards in the bank's decks. Educators; learners study shared.", "operationId": "browse_flashcards_api_v1_flashcards_cards_browse_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "q", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Q"}}, {"name": "deck_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Deck Id"}}, {"name": "tag_ids", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Comma-separated tag IDs (AND filter)", "title": "Tag Ids"}, "description": "Comma-separated tag IDs (AND filter)"}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 200, "default": 50, "title": "Limit"}}, {"name": "offset", "in": "query", "required": false, "schema": {"type": "integer", "default": 0, "title": "Offset"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/cards/browse/ids": {"get": {"tags": ["flashcards"], "summary": "Browse Flashcard Ids", "description": "Return just IDs for matching cards (for select-all).", "operationId": "browse_flashcard_ids_api_v1_flashcards_cards_browse_ids_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "q", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Q"}}, {"name": "deck_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Deck Id"}}, {"name": "tag_ids", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Tag Ids"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/cards/{card_id}": {"put": {"tags": ["flashcards"], "summary": "Update Flashcard", "description": "Edit a single flashcard (front, back). Owner or admin.", "operationId": "update_flashcard_api_v1_flashcards_cards__card_id__put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "card_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Card Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CardEdit"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["flashcards"], "summary": "Delete Flashcard", "description": "Delete a card, and its mirror if it has one. Educator, or granted.", "operationId": "delete_flashcard_api_v1_flashcards_cards__card_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "card_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Card Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/decks/{deck_id}/cards": {"post": {"tags": ["flashcards"], "summary": "Create Flashcard Manually", "description": "Write a card into a deck. Educator, or somebody granted this deck.\n\nOne card written is not always one row stored: a cloze with three\ndeletions is three cards over one passage, and a reversed card is a pair.\n`card_types.rows_for` is the only place that knows which, so everything\ndownstream still deals in rows.", "operationId": "create_flashcard_manually_api_v1_flashcards_decks__deck_id__cards_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "deck_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Deck Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CardCreate"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/cards/{card_id}/links": {"get": {"tags": ["flashcards"], "summary": "List Card Links", "description": "Questions and articles associated with a card (access follows the deck).", "operationId": "list_card_links_api_v1_flashcards_cards__card_id__links_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "card_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Card Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/cards/{card_id}/links/question": {"put": {"tags": ["flashcards"], "summary": "Link Card Question", "operationId": "link_card_question_api_v1_flashcards_cards__card_id__links_question_put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "card_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Card Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CardQuestionLinkIn"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/cards/{card_id}/links/question/{question_id}": {"delete": {"tags": ["flashcards"], "summary": "Unlink Card Question", "operationId": "unlink_card_question_api_v1_flashcards_cards__card_id__links_question__question_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "card_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Card Id"}}, {"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/cards/{card_id}/links/article": {"put": {"tags": ["flashcards"], "summary": "Link Card Article", "operationId": "link_card_article_api_v1_flashcards_cards__card_id__links_article_put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "card_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Card Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CardArticleLinkIn"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/cards/{card_id}/links/article/{article_id}": {"delete": {"tags": ["flashcards"], "summary": "Unlink Card Article", "operationId": "unlink_card_article_api_v1_flashcards_cards__card_id__links_article__article_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "card_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Card Id"}}, {"name": "article_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Article Id"}}, {"name": "article_section_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Article Section Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/cards/linked": {"get": {"tags": ["flashcards"], "summary": "Cards For Target", "description": "Cards linked to a question or article; results follow deck sharing/ownership.", "operationId": "cards_for_target_api_v1_flashcards_cards_linked_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "question_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Question Id"}}, {"name": "article_id", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Article Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/queue": {"get": {"tags": ["flashcards"], "summary": "Daily Queue", "description": "Everything due across every shared deck, then a capped run of new cards.\n\nPer learner, not per deck. Studying deck by deck means the cards you are\nworst at are spread across ten screens and the ones you know come round\nagain because they happen to sit in the deck you opened.\n\nNew cards are drawn from the categories this learner is weakest in, so the\ntwenty they meet today are twenty they need rather than twenty in id\norder \u2014 the same ranking the tutor's starter questions use.", "operationId": "daily_queue_api_v1_flashcards_queue_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 200, "minimum": 1, "default": 60, "title": "Limit"}}, {"name": "new_limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 100, "minimum": 0, "default": 20, "title": "New Limit"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/flashcards/preview/{deck_id}": {"get": {"tags": ["flashcards"], "summary": "Preview Deck", "description": "What a `[[deck:12]]` marker points at, for a hover card.\n\nThe same shape as an article preview, because it is drawn by the same\ncard. Small on purpose: a marker in a question explanation must not pull\ndown a deck of two hundred cards to say what it is.", "operationId": "preview_deck_api_v1_flashcards_preview__deck_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "deck_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Deck Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-tools/lab-values": {"get": {"tags": ["study-tools"], "summary": "Lab Values", "operationId": "lab_values_api_v1_study_tools_lab_values_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "include_drafts", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Include Drafts"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "post": {"tags": ["study-tools"], "summary": "Create Lab Value", "operationId": "create_lab_value_api_v1_study_tools_lab_values_post", "security": [{"OAuth2PasswordBearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LabInput"}}}}, "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-tools/lab-values/{entry_id}": {"put": {"tags": ["study-tools"], "summary": "Update Lab Value", "operationId": "update_lab_value_api_v1_study_tools_lab_values__entry_id__put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "entry_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Entry Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LabInput"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["study-tools"], "summary": "Delete Lab Value", "operationId": "delete_lab_value_api_v1_study_tools_lab_values__entry_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "entry_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Entry Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-tools/lab-values/{entry_id}/cards/{card_id}": {"put": {"tags": ["study-tools"], "summary": "Link Lab Card", "operationId": "link_lab_card_api_v1_study_tools_lab_values__entry_id__cards__card_id__put", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "entry_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Entry Id"}}, {"name": "card_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Card Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["study-tools"], "summary": "Unlink Lab Card", "operationId": "unlink_lab_card_api_v1_study_tools_lab_values__entry_id__cards__card_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "entry_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Entry Id"}}, {"name": "card_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Card Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-tools/performance-by-category": {"get": {"tags": ["study-tools"], "summary": "Performance By Category", "description": "Accuracy per category from the user's completed, non-expired general-bank answers.\nA question counts in its primary category and every additional category link.", "operationId": "performance_by_category_api_v1_study_tools_performance_by_category_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/study-tools/completion": {"get": {"tags": ["study-tools"], "summary": "Completion", "description": "How much of the bank has been worked through, and at what pace.\n\nOver a window, because \"how am I doing\" and \"how was I doing last month\"\nare different questions and one figure cannot answer both. No window means\neverything.\n\nRepetitions are excluded for the same reason they are excluded everywhere\nelse: sitting a question you have already seen the answer to is practice,\nnot a measurement.", "operationId": "completion_api_v1_study_tools_completion_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "days", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer", "maximum": 3650, "minimum": 1}, {"type": "null"}], "title": "Days"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-tools/answer-split": {"get": {"tags": ["study-tools"], "summary": "Answer Split", "description": "The same answers counted two ways.\n\n*All attempts* is every answer ever given: it says how much work has been\ndone. *Latest attempt* keeps only the most recent answer to each question:\nit says what is known now. A learner who got a question wrong in March and\nright in September is at 50% by the first measure and 100% by the second,\nand both are true statements about different questions.\n\nRepetitions and expired attempts are left out, as\neverywhere else that measures rather than counts practice.", "operationId": "answer_split_api_v1_study_tools_answer_split_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/study-tools/performance-over-time": {"get": {"tags": ["study-tools"], "summary": "Performance Over Time", "description": "The headline score by date.\n\nTwo lines are meant here, and only one of them is the score. A session's\nown percentage swings with whatever twelve questions it happened to hold;\nthe running figure \u2014 everything answered up to that day \u2014 is the one that\nsays whether the learner is getting better. The chart draws the running\nline and marks the sessions along it.\n\nIt stays locked until there are enough answers to mean anything, for the\nsame reason readiness does: a line through two points is a decoration.", "operationId": "performance_over_time_api_v1_study_tools_performance_over_time_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/study-tools/readiness": {"get": {"tags": ["study-tools"], "summary": "Readiness", "description": "Two figures, each refusing to appear before it means anything.\n\n**Your score** is the share of questions you got right at your most recent\nattempt at each. Not an equated score: we do not have the psychometrics to\nequate one, and a number dressed up as one would be a claim we cannot\nsupport.\n\n**Against everyone else** compares that with how other learners did on the\nvery questions you answered, rather than with their scores on whatever they\nhappened to sit. Someone who worked through the hardest fifty in the bank\nshould not read as weaker than someone who did fifty easy ones, and a\npercentile over different question sets says exactly that.", "operationId": "readiness_api_v1_study_tools_readiness_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/study-tools/recommendations": {"get": {"tags": ["study-tools"], "summary": "Study Recommendations", "description": "Focus areas ranked by the study time most likely to raise the learner's score.\n\nReadiness is the learner's accuracy in a category shrunk toward their own\noverall accuracy in proportion to how few answers that category has, so a\nsingle unlucky question does not read as a knowledge gap. It is a plain\nempirical-Bayes estimate over recorded answers \u2014 not a psychometric exam\nscore, and not a prediction of any real examination.", "operationId": "study_recommendations_api_v1_study_tools_recommendations_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "group", "in": "query", "required": false, "schema": {"enum": ["articles", "disciplines", "systems"], "type": "string", "default": "disciplines", "title": "Group"}}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 60, "minimum": 1, "default": 20, "title": "Limit"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/study-tools/attempts/{attempt_id}/questions/{question_id}/responses": {"get": {"tags": ["study-tools"], "summary": "Question Responses", "operationId": "question_responses_api_v1_study_tools_attempts__attempt_id__questions__question_id__responses_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "attempt_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Attempt Id"}}, {"name": "question_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Question Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/search": {"get": {"tags": ["search"], "summary": "Search Everything", "description": "Search every corpus at once, grouped by what was found.", "operationId": "search_everything_api_v1_search_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "q", "in": "query", "required": false, "schema": {"type": "string", "maxLength": 500, "default": "", "title": "Q"}}, {"name": "kinds", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Comma-separated subset of article,flashcard,media", "title": "Kinds"}, "description": "Comma-separated subset of article,flashcard,media"}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 50, "minimum": 1, "default": 10, "title": "Limit"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/search/suggest": {"get": {"tags": ["search"], "summary": "Suggest", "description": "Titles for a typeahead \u2014 cheap enough to run on every keystroke.\n\nDeliberately lexical and prefix-based: a typeahead is finishing the word you\nare typing, and a semantic neighbour of half a word is noise. No embedding\nand no reranker for the same reason \u2014 both would be asked to judge relevance\nto half a word, at a round trip each, several times a second.", "operationId": "suggest_api_v1_search_suggest_get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "q", "in": "query", "required": false, "schema": {"type": "string", "maxLength": 200, "default": "", "title": "Q"}}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "maximum": 12, "minimum": 1, "default": 6, "title": "Limit"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/ai/conversations": {"get": {"tags": ["ai-mode"], "summary": "List Conversations", "description": "Every thread of this learner's, tidying the empty ones as it goes.", "operationId": "list_conversations_api_v1_ai_conversations_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}, "post": {"tags": ["ai-mode"], "summary": "Create Conversation", "operationId": "create_conversation_api_v1_ai_conversations_post", "responses": {"201": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}, "security": [{"OAuth2PasswordBearer": []}]}}, "/api/v1/ai/conversations/{conversation_id}": {"get": {"tags": ["ai-mode"], "summary": "Get Conversation", "operationId": "get_conversation_api_v1_ai_conversations__conversation_id__get", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "conversation_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Conversation Id"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "patch": {"tags": ["ai-mode"], "summary": "Rename Conversation", "operationId": "rename_conversation_api_v1_ai_conversations__conversation_id__patch", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "conversation_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Conversation Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConversationRename"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["ai-mode"], "summary": "Delete Conversation", "operationId": "delete_conversation_api_v1_ai_conversations__conversation_id__delete", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "conversation_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Conversation Id"}}], "responses": {"204": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/ai/conversations/{conversation_id}/practice": {"post": {"tags": ["ai-mode"], "summary": "Practice", "description": "Turn a chat turn into a study session.\n\nThe chat is for finding out what you do not know; the point of finding out\nis to go and practise it. This is the step between \u2014 it builds a session\nfrom what the answer actually cited, so the questions follow from the\nconversation rather than from the topic in general.\n\nStudy mode, never exam: this is reading followed by practice, not a paper.", "operationId": "practice_api_v1_ai_conversations__conversation_id__practice_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "conversation_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Conversation Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PracticeIn"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/v1/ai/conversations/{conversation_id}/messages": {"post": {"tags": ["ai-mode"], "summary": "Ask", "description": "Answer from the learner's library, citing only what retrieval found.", "operationId": "ask_api_v1_ai_conversations__conversation_id__messages_post", "security": [{"OAuth2PasswordBearer": []}], "parameters": [{"name": "conversation_id", "in": "path", "required": true, "schema": {"type": "integer", "title": "Conversation Id"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AskIn"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/health": {"get": {"tags": ["health"], "summary": "Health Check", "description": "Is the process up. Deliberately unversioned as well as versioned: the\nthing that watches this is a monitor nobody edits for a year.", "operationId": "health_check_api_health_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}}, "components": {"schemas": {"AIModelConfigCreate": {"properties": {"name": {"type": "string", "title": "Name"}, "model_id": {"type": "string", "title": "Model Id"}, "task": {"type": "string", "title": "Task"}, "api_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Api Key"}, "is_active": {"type": "boolean", "title": "Is Active", "default": true}, "is_default": {"type": "boolean", "title": "Is Default", "default": false}}, "type": "object", "required": ["name", "model_id", "task"], "title": "AIModelConfigCreate"}, "AIModelConfigResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "name": {"type": "string", "title": "Name"}, "model_id": {"type": "string", "title": "Model Id"}, "task": {"type": "string", "title": "Task"}, "is_active": {"type": "boolean", "title": "Is Active"}, "is_default": {"type": "boolean", "title": "Is Default"}, "created_at": {"type": "string", "format": "date-time", "title": "Created At"}}, "type": "object", "required": ["id", "name", "model_id", "task", "is_active", "is_default", "created_at"], "title": "AIModelConfigResponse"}, "AIModelConfigUpdate": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "model_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Model Id"}, "task": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Task"}, "api_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Api Key"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}, "is_default": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Default"}}, "type": "object", "title": "AIModelConfigUpdate"}, "ActiveExam": {"properties": {"exam_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Exam Id"}}, "type": "object", "title": "ActiveExam"}, "AliasSet": {"properties": {"aliases": {"additionalProperties": {"items": {"type": "string"}, "type": "array"}, "type": "object", "title": "Aliases"}}, "type": "object", "required": ["aliases"], "title": "AliasSet", "description": "The whole alias vocabulary, keyed by article id as a string."}, "AnswerDetail": {"properties": {"question_id": {"type": "integer", "title": "Question Id"}, "question_text": {"type": "string", "title": "Question Text"}, "question_type": {"type": "string", "title": "Question Type"}, "options": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Options"}, "user_answer": {"type": "string", "title": "User Answer"}, "correct_answer": {"type": "string", "title": "Correct Answer"}, "is_correct": {"type": "boolean", "title": "Is Correct"}, "withdrawn": {"type": "boolean", "title": "Withdrawn", "default": false}, "explanation": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Explanation"}, "option_explanations": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "title": "Option Explanations"}, "explanation_image_path": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Explanation Image Path"}, "image_path": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Path"}, "page_reference": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Page Reference"}, "category_breadcrumbs": {"items": {"additionalProperties": true, "type": "object"}, "type": "array", "title": "Category Breadcrumbs"}, "figures": {"items": {"additionalProperties": true, "type": "object"}, "type": "array", "title": "Figures"}}, "type": "object", "required": ["question_id", "question_text", "question_type", "user_answer", "correct_answer", "is_correct", "explanation"], "title": "AnswerDetail"}, "AnswerRecord": {"properties": {"question_id": {"type": "integer", "title": "Question Id"}, "user_answer": {"type": "string", "title": "User Answer"}, "seconds_spent": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Seconds Spent"}, "used_hint": {"type": "boolean", "title": "Used Hint", "default": false}}, "type": "object", "required": ["question_id", "user_answer"], "title": "AnswerRecord"}, "AnswerSubmission": {"properties": {"question_id": {"type": "integer", "title": "Question Id"}, "user_answer": {"type": "string", "title": "User Answer"}}, "type": "object", "required": ["question_id", "user_answer"], "title": "AnswerSubmission"}, "ArticleAIDraft": {"properties": {"topic": {"type": "string", "title": "Topic"}, "instructions": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Instructions"}, "use_library": {"type": "boolean", "title": "Use Library", "default": false}, "use_pubmed": {"type": "boolean", "title": "Use Pubmed", "default": false}}, "type": "object", "required": ["topic"], "title": "ArticleAIDraft"}, "ArticleAIRefine": {"properties": {"instructions": {"type": "string", "title": "Instructions", "default": ""}, "use_library": {"type": "boolean", "title": "Use Library", "default": false}, "use_pubmed": {"type": "boolean", "title": "Use Pubmed", "default": false}}, "type": "object", "title": "ArticleAIRefine"}, "ArticleCategoryLinkIn": {"properties": {"category_id": {"type": "integer", "title": "Category Id"}, "section_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Section Id"}, "include_subtopics": {"type": "boolean", "title": "Include Subtopics", "default": true}, "fill_only": {"type": "boolean", "title": "Fill Only", "default": false}}, "type": "object", "required": ["category_id"], "title": "ArticleCategoryLinkIn"}, "ArticleFeedbackIn": {"properties": {"message": {"type": "string", "maxLength": 2000, "minLength": 3, "title": "Message"}, "section_id": {"anyOf": [{"type": "string", "maxLength": 64}, {"type": "null"}], "title": "Section Id"}}, "type": "object", "required": ["message"], "title": "ArticleFeedbackIn"}, "ArticleLinkIn": {"properties": {"question_id": {"type": "integer", "title": "Question Id"}, "section_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Section Id"}}, "type": "object", "required": ["question_id"], "title": "ArticleLinkIn"}, "ArticlePublish": {"properties": {"published": {"type": "boolean", "title": "Published"}}, "type": "object", "required": ["published"], "title": "ArticlePublish"}, "ArticleReference": {"properties": {"title": {"type": "string", "maxLength": 300, "minLength": 1, "title": "Title"}, "author": {"anyOf": [{"type": "string", "maxLength": 300}, {"type": "null"}], "title": "Author"}, "pages": {"items": {"type": "integer"}, "type": "array", "maxItems": 40, "title": "Pages"}, "journal": {"anyOf": [{"type": "string", "maxLength": 150}, {"type": "null"}], "title": "Journal"}, "year": {"anyOf": [{"type": "string", "maxLength": 10}, {"type": "null"}], "title": "Year"}, "pmid": {"anyOf": [{"type": "string", "maxLength": 20}, {"type": "null"}], "title": "Pmid"}, "text": {"anyOf": [{"type": "string", "maxLength": 600}, {"type": "null"}], "title": "Text"}, "doi": {"anyOf": [{"type": "string", "maxLength": 200}, {"type": "null"}], "title": "Doi"}, "url": {"anyOf": [{"type": "string", "maxLength": 600}, {"type": "null"}], "title": "Url"}}, "type": "object", "required": ["title"], "title": "ArticleReference", "description": "Where a fact came from. Named sources, not markers in the prose.\n\nTwo kinds share the shape. A book has an author and the pages a fact is\non; a paper has a journal, a year and a PMID somebody can look up. The\nfields a kind does not use stay empty rather than each kind getting its\nown list, because the reader draws one reference list."}, "ArticleSection": {"properties": {"id": {"type": "string", "title": "Id"}, "slug": {"type": "string", "title": "Slug"}, "title": {"type": "string", "title": "Title"}, "content": {"type": "string", "title": "Content", "default": ""}, "parent_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Parent Id"}, "variant": {"type": "string", "title": "Variant", "default": "long"}}, "type": "object", "required": ["id", "slug", "title"], "title": "ArticleSection"}, "ArticleWrite": {"properties": {"title": {"type": "string", "title": "Title"}, "slug": {"type": "string", "title": "Slug"}, "summary": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Summary"}, "content": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Content"}, "sections": {"items": {"$ref": "#/components/schemas/ArticleSection"}, "type": "array", "title": "Sections", "default": []}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}, "section_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Section Id"}, "references": {"anyOf": [{"items": {"$ref": "#/components/schemas/ArticleReference"}, "type": "array"}, {"type": "null"}], "title": "References"}}, "type": "object", "required": ["title", "slug"], "title": "ArticleWrite"}, "AskIn": {"properties": {"message": {"type": "string", "maxLength": 2000, "minLength": 1, "title": "Message"}, "image": {"anyOf": [{"type": "string", "maxLength": 3145728}, {"type": "null"}], "title": "Image"}}, "type": "object", "required": ["message"], "title": "AskIn"}, "AssignByCategory": {"properties": {"category_ids": {"items": {"type": "integer"}, "type": "array", "minItems": 1, "title": "Category Ids"}, "include_descendants": {"type": "boolean", "title": "Include Descendants", "default": true}, "questions": {"type": "boolean", "title": "Questions", "default": true}, "articles": {"type": "boolean", "title": "Articles", "default": true}}, "type": "object", "required": ["category_ids"], "title": "AssignByCategory"}, "AttemptDetail": {"properties": {"id": {"type": "integer", "title": "Id"}, "quiz_id": {"type": "integer", "title": "Quiz Id"}, "score": {"type": "integer", "title": "Score"}, "total_questions": {"type": "integer", "title": "Total Questions"}, "percentage": {"type": "number", "title": "Percentage"}, "started_at": {"type": "string", "format": "date-time", "title": "Started At"}, "completed_at": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}], "title": "Completed At"}, "mode": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Mode"}, "answers": {"items": {"$ref": "#/components/schemas/AnswerDetail"}, "type": "array", "title": "Answers", "default": []}}, "type": "object", "required": ["id", "quiz_id", "score", "total_questions", "percentage", "started_at", "completed_at"], "title": "AttemptDetail"}, "AttemptResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "quiz_id": {"type": "integer", "title": "Quiz Id"}, "score": {"type": "integer", "title": "Score"}, "total_questions": {"type": "integer", "title": "Total Questions"}, "percentage": {"type": "number", "title": "Percentage"}, "started_at": {"type": "string", "format": "date-time", "title": "Started At"}, "completed_at": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}], "title": "Completed At"}, "mode": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Mode"}}, "type": "object", "required": ["id", "quiz_id", "score", "total_questions", "percentage", "started_at", "completed_at"], "title": "AttemptResponse"}, "AttemptSubmit": {"properties": {"answers": {"items": {"$ref": "#/components/schemas/AnswerSubmission"}, "type": "array", "title": "Answers"}, "timings": {"anyOf": [{"additionalProperties": {"type": "integer"}, "type": "object"}, {"type": "null"}], "title": "Timings"}, "hints": {"anyOf": [{"items": {"type": "integer"}, "type": "array"}, {"type": "null"}], "title": "Hints"}}, "type": "object", "required": ["answers"], "title": "AttemptSubmit"}, "BatchEdit": {"properties": {"title": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Title"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}, "status": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Status"}}, "type": "object", "title": "BatchEdit"}, "BlockArticleIn": {"properties": {"article_id": {"type": "integer", "title": "Article Id"}}, "type": "object", "required": ["article_id"], "title": "BlockArticleIn"}, "BlockOrder": {"properties": {"block_ids": {"items": {"type": "integer"}, "type": "array", "minItems": 1, "title": "Block Ids"}}, "type": "object", "required": ["block_ids"], "title": "BlockOrder"}, "BlockUpdate": {"properties": {"title": {"anyOf": [{"type": "string", "maxLength": 200, "minLength": 1}, {"type": "null"}], "title": "Title"}, "question_ids": {"anyOf": [{"items": {"type": "integer"}, "type": "array"}, {"type": "null"}], "title": "Question Ids"}}, "type": "object", "title": "BlockUpdate"}, "BlockWrite": {"properties": {"title": {"type": "string", "maxLength": 200, "minLength": 1, "title": "Title"}, "question_ids": {"items": {"type": "integer"}, "type": "array", "title": "Question Ids", "default": []}}, "type": "object", "required": ["title"], "title": "BlockWrite"}, "BlueprintCategories": {"properties": {"category_ids": {"items": {"type": "integer"}, "type": "array", "title": "Category Ids"}}, "type": "object", "required": ["category_ids"], "title": "BlueprintCategories"}, "BlueprintPlan": {"properties": {"exam_id": {"type": "integer", "title": "Exam Id"}, "slug": {"type": "string", "title": "Slug"}, "name": {"type": "string", "title": "Name"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "shape": {"type": "string", "title": "Shape", "default": "domains"}, "block_size": {"type": "integer", "maximum": 200.0, "minimum": 5.0, "title": "Block Size", "default": 40}, "blocks": {"type": "integer", "maximum": 80.0, "minimum": 1.0, "title": "Blocks", "default": 24}, "sort_order": {"type": "integer", "title": "Sort Order", "default": 100}, "is_published": {"type": "boolean", "title": "Is Published", "default": true}, "with_reading": {"type": "boolean", "title": "With Reading", "default": true}}, "type": "object", "required": ["exam_id", "slug", "name"], "title": "BlueprintPlan"}, "Body_add_article_image_api_v1_articles__article_id__figures_image_post": {"properties": {"section_id": {"type": "string", "title": "Section Id"}, "file": {"type": "string", "format": "binary", "title": "File"}, "title": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Title"}, "caption": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Caption"}, "source": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Source"}, "source_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Source Url"}, "license": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "License"}, "replace": {"type": "boolean", "title": "Replace", "default": false}}, "type": "object", "required": ["section_id", "file"], "title": "Body_add_article_image_api_v1_articles__article_id__figures_image_post"}, "Body_build_test_from_upload_api_v1_questions_builder_from_upload_post": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}, "count": {"type": "integer", "title": "Count", "default": 20}, "mode": {"type": "string", "title": "Mode", "default": "learning"}}, "type": "object", "required": ["file"], "title": "Body_build_test_from_upload_api_v1_questions_builder_from_upload_post"}, "Body_import_qti_api_v1_drafts_import_qti_post": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_import_qti_api_v1_drafts_import_qti_post"}, "Body_import_spreadsheet_api_v1_drafts_import_post": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_import_spreadsheet_api_v1_drafts_import_post"}, "Body_speech_to_text_api_v1_tts_transcribe_post": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_speech_to_text_api_v1_tts_transcribe_post"}, "Body_upload_document_api_v1_documents_upload_post": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_upload_document_api_v1_documents_upload_post"}, "Body_upload_media_api_v1_media_upload_post": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}, "library_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Library Id"}, "title": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Title"}, "caption": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Caption"}, "alt_text": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Alt Text"}, "source": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Source"}, "source_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Source Url"}}, "type": "object", "required": ["file"], "title": "Body_upload_media_api_v1_media_upload_post"}, "Body_upload_question_image_api_v1_questions_upload_image_post": {"properties": {"file": {"type": "string", "format": "binary", "title": "File"}}, "type": "object", "required": ["file"], "title": "Body_upload_question_image_api_v1_questions_upload_image_post"}, "BulkCategoryRequest": {"properties": {"question_ids": {"items": {"type": "integer"}, "type": "array", "title": "Question Ids"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}}, "type": "object", "required": ["question_ids"], "title": "BulkCategoryRequest"}, "BulkIn": {"properties": {"terms": {"items": {"$ref": "#/components/schemas/TermIn"}, "type": "array", "maxItems": 200, "minItems": 1, "title": "Terms"}}, "type": "object", "required": ["terms"], "title": "BulkIn"}, "BulkOut": {"properties": {"written": {"type": "integer", "title": "Written"}, "terms": {"items": {"$ref": "#/components/schemas/TermOut"}, "type": "array", "title": "Terms"}}, "type": "object", "required": ["written", "terms"], "title": "BulkOut"}, "BulkQuestionAction": {"properties": {"question_ids": {"items": {"type": "integer"}, "type": "array", "title": "Question Ids"}, "action": {"type": "string", "enum": ["category", "difficulty", "delete"], "title": "Action"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}, "difficulty": {"anyOf": [{"type": "string", "enum": ["easy", "medium", "hard"]}, {"type": "null"}], "title": "Difficulty"}}, "type": "object", "required": ["question_ids", "action"], "title": "BulkQuestionAction", "description": "One editorial action applied to a checked set in the question manager."}, "CardArticleLinkIn": {"properties": {"article_id": {"type": "integer", "title": "Article Id"}, "article_section_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Article Section Id"}}, "type": "object", "required": ["article_id"], "title": "CardArticleLinkIn"}, "CardCreate": {"properties": {"front": {"type": "string", "title": "Front"}, "back": {"type": "string", "title": "Back"}, "image_path": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Path"}, "card_type": {"type": "string", "title": "Card Type", "default": "basic"}, "image_side": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Side"}}, "type": "object", "required": ["front", "back"], "title": "CardCreate"}, "CardEdit": {"properties": {"front": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Front"}, "back": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Back"}, "card_type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Card Type"}, "image_side": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Side"}, "image_path": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Path"}}, "type": "object", "title": "CardEdit"}, "CardQuestionLinkIn": {"properties": {"question_id": {"type": "integer", "title": "Question Id"}}, "type": "object", "required": ["question_id"], "title": "CardQuestionLinkIn"}, "CardVerdict": {"properties": {"outcome": {"anyOf": [{"type": "string", "enum": ["known", "again"]}, {"type": "null"}], "title": "Outcome"}, "rating": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Rating"}}, "type": "object", "title": "CardVerdict", "description": "What a learner said about a card.\n\nTwo shapes accepted. `rating` is the four-point scale the study page now\nsends \u2014 Again, Hard, Good, Easy \u2014 and the one that schedules the card.\n`outcome` is what it sent before, kept so a tab left open across the\ndeploy does not start failing; it maps to Again and Good."}, "CategoryCreate": {"properties": {"name": {"type": "string", "title": "Name"}}, "type": "object", "required": ["name"], "title": "CategoryCreate"}, "CategoryResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "name": {"type": "string", "title": "Name"}, "quiz_count": {"type": "integer", "title": "Quiz Count", "default": 0}}, "type": "object", "required": ["id", "name"], "title": "CategoryResponse"}, "ChatMessage": {"properties": {"role": {"type": "string", "title": "Role"}, "content": {"type": "string", "title": "Content"}}, "type": "object", "required": ["role", "content"], "title": "ChatMessage"}, "ChatRequest": {"properties": {"question_id": {"type": "integer", "title": "Question Id"}, "attempt_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Attempt Id"}, "messages": {"items": {"$ref": "#/components/schemas/ChatMessage"}, "type": "array", "title": "Messages"}, "model_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Model Id"}}, "type": "object", "required": ["question_id", "messages"], "title": "ChatRequest"}, "CollectionCreate": {"properties": {"title": {"type": "string", "title": "Title"}}, "type": "object", "required": ["title"], "title": "CollectionCreate"}, "ContactRequest": {"properties": {"name": {"type": "string", "title": "Name"}, "email": {"type": "string", "format": "email", "title": "Email"}, "type": {"type": "string", "title": "Type"}, "message": {"type": "string", "title": "Message"}}, "type": "object", "required": ["name", "email", "type", "message"], "title": "ContactRequest"}, "ConversationRename": {"properties": {"title": {"type": "string", "maxLength": 200, "minLength": 1, "title": "Title"}}, "type": "object", "required": ["title"], "title": "ConversationRename"}, "CreateFromBankRequest": {"properties": {"title": {"type": "string", "maxLength": 200, "minLength": 1, "title": "Title"}, "mode": {"type": "string", "enum": ["timed", "learning"], "title": "Mode", "default": "timed"}, "time_limit_minutes": {"anyOf": [{"type": "integer", "exclusiveMinimum": 0.0}, {"type": "null"}], "title": "Time Limit Minutes"}, "is_shared": {"type": "boolean", "title": "Is Shared", "default": false}, "is_repetition": {"type": "boolean", "title": "Is Repetition", "default": false}, "question_ids": {"items": {"type": "integer"}, "type": "array", "minItems": 1, "title": "Question Ids"}}, "type": "object", "required": ["title", "question_ids"], "title": "CreateFromBankRequest"}, "CrosslinkApply": {"properties": {"proposals": {"items": {"additionalProperties": true, "type": "object"}, "type": "array", "title": "Proposals"}}, "type": "object", "required": ["proposals"], "title": "CrosslinkApply"}, "DashboardStats": {"properties": {"total_documents": {"type": "integer", "title": "Total Documents"}, "total_quizzes": {"type": "integer", "title": "Total Quizzes"}, "total_attempts": {"type": "integer", "title": "Total Attempts"}, "average_score": {"type": "number", "title": "Average Score"}, "quiz_stats": {"items": {"$ref": "#/components/schemas/QuizStats"}, "type": "array", "title": "Quiz Stats", "default": []}, "questions_seen": {"type": "integer", "title": "Questions Seen", "default": 0}, "questions_correct": {"type": "integer", "title": "Questions Correct", "default": 0}, "bank_total": {"type": "integer", "title": "Bank Total", "default": 0}}, "type": "object", "required": ["total_documents", "total_quizzes", "total_attempts", "average_score"], "title": "DashboardStats"}, "Decision": {"properties": {"ids": {"items": {"type": "integer"}, "type": "array", "maxItems": 200, "minItems": 1, "title": "Ids"}, "note": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Note"}}, "type": "object", "required": ["ids"], "title": "Decision"}, "DeckGrantIn": {"properties": {"user_id": {"type": "integer", "title": "User Id"}, "deck_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Deck Id"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}}, "type": "object", "required": ["user_id"], "title": "DeckGrantIn"}, "DescribeRequest": {"properties": {"text": {"type": "string", "maxLength": 2000, "minLength": 10, "title": "Text"}, "count": {"type": "integer", "maximum": 30.0, "minimum": 1.0, "title": "Count", "default": 20}, "mode": {"type": "string", "enum": ["learning", "timed"], "title": "Mode", "default": "learning"}, "title": {"anyOf": [{"type": "string", "maxLength": 200}, {"type": "null"}], "title": "Title"}}, "type": "object", "required": ["text"], "title": "DescribeRequest", "description": "Free-text description of what the learner wants to study."}, "DocumentResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "user_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "User Id"}, "original_filename": {"type": "string", "title": "Original Filename"}, "total_pages": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Total Pages"}, "status": {"type": "string", "title": "Status"}, "error_message": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error Message"}, "uploaded_at": {"type": "string", "format": "date-time", "title": "Uploaded At"}, "sections": {"items": {"$ref": "#/components/schemas/SectionResponse"}, "type": "array", "title": "Sections", "default": []}}, "type": "object", "required": ["id", "original_filename", "total_pages", "status", "error_message", "uploaded_at"], "title": "DocumentResponse"}, "DocumentStatusResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "status": {"type": "string", "title": "Status"}, "total_pages": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Total Pages"}, "error_message": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error Message"}}, "type": "object", "required": ["id", "status", "total_pages", "error_message"], "title": "DocumentStatusResponse"}, "DraftEdit": {"properties": {"question_text": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Question Text"}, "question_type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Question Type"}, "options": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Options"}, "correct_answer": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Correct Answer"}, "explanation": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Explanation"}, "option_explanations": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "title": "Option Explanations"}, "key_points": {"anyOf": [{"items": {"additionalProperties": true, "type": "object"}, "type": "array"}, {"type": "null"}], "title": "Key Points"}, "attending_tip": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Attending Tip"}, "difficulty": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Difficulty"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}}, "type": "object", "title": "DraftEdit"}, "DraftExtractionCreate": {"properties": {"section_id": {"type": "integer", "title": "Section Id"}, "extraction_mode": {"type": "string", "title": "Extraction Mode", "default": "standard"}, "model_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Model Id"}}, "type": "object", "required": ["section_id"], "title": "DraftExtractionCreate"}, "ExamEdit": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "sort_order": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Sort Order"}, "is_active": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Is Active"}, "family": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Family"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "article_views": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Article Views"}}, "type": "object", "title": "ExamEdit"}, "ExamWrite": {"properties": {"name": {"type": "string", "title": "Name"}, "slug": {"type": "string", "title": "Slug"}, "sort_order": {"type": "integer", "title": "Sort Order", "default": 100}, "is_active": {"type": "integer", "title": "Is Active", "default": 1}, "family": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Family"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "article_views": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Article Views"}}, "type": "object", "required": ["name", "slug"], "title": "ExamWrite"}, "FavoriteCreate": {"properties": {"question_id": {"type": "integer", "title": "Question Id"}}, "type": "object", "required": ["question_id"], "title": "FavoriteCreate"}, "FavoriteResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "user_id": {"type": "integer", "title": "User Id"}, "question_id": {"type": "integer", "title": "Question Id"}, "created_at": {"type": "string", "format": "date-time", "title": "Created At"}}, "type": "object", "required": ["id", "user_id", "question_id", "created_at"], "title": "FavoriteResponse"}, "FeedbackIn": {"properties": {"message": {"type": "string", "maxLength": 2000, "minLength": 3, "title": "Message"}}, "type": "object", "required": ["message"], "title": "FeedbackIn"}, "FigureUpdate": {"properties": {"label": {"anyOf": [{"type": "string", "maxLength": 80}, {"type": "null"}], "title": "Label"}, "caption": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Caption"}, "role": {"anyOf": [{"type": "string", "enum": ["stem", "explanation"]}, {"type": "null"}], "title": "Role"}, "position": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Position"}}, "type": "object", "title": "FigureUpdate"}, "FlashcardCardResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "deck_id": {"type": "integer", "title": "Deck Id"}, "front": {"type": "string", "title": "Front"}, "back": {"type": "string", "title": "Back"}, "page_reference": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Page Reference"}, "image_path": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Path"}, "created_at": {"title": "Created At"}}, "type": "object", "required": ["id", "deck_id", "front", "back", "created_at"], "title": "FlashcardCardResponse"}, "FlashcardDeckDetail": {"properties": {"id": {"type": "integer", "title": "Id"}, "title": {"type": "string", "title": "Title"}, "section_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Section Id"}, "user_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "User Id"}, "card_count": {"type": "integer", "title": "Card Count"}, "created_at": {"title": "Created At"}, "cards": {"items": {"$ref": "#/components/schemas/FlashcardCardResponse"}, "type": "array", "title": "Cards"}}, "type": "object", "required": ["id", "title", "card_count", "created_at", "cards"], "title": "FlashcardDeckDetail"}, "FlashcardDeckResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "title": {"type": "string", "title": "Title"}, "section_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Section Id"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}, "user_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "User Id"}, "card_count": {"type": "integer", "title": "Card Count"}, "is_shared": {"type": "integer", "title": "Is Shared", "default": 0}, "created_at": {"title": "Created At"}, "deleted_at": {"anyOf": [{}, {"type": "null"}], "title": "Deleted At"}}, "type": "object", "required": ["id", "title", "card_count", "created_at"], "title": "FlashcardDeckResponse"}, "FlashcardDeckUpdate": {"properties": {"title": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Title"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}}, "type": "object", "title": "FlashcardDeckUpdate"}, "FolderQuestions": {"properties": {"question_ids": {"items": {"type": "integer"}, "type": "array", "maxItems": 500, "minItems": 1, "title": "Question Ids"}}, "type": "object", "required": ["question_ids"], "title": "FolderQuestions"}, "FolderWrite": {"properties": {"name": {"type": "string", "maxLength": 200, "minLength": 1, "title": "Name"}, "description": {"anyOf": [{"type": "string", "maxLength": 2000}, {"type": "null"}], "title": "Description"}}, "type": "object", "required": ["name"], "title": "FolderWrite"}, "GenerateTestRequest": {"properties": {"title": {"type": "string", "maxLength": 200, "minLength": 1, "title": "Title"}, "mode": {"type": "string", "enum": ["timed", "learning"], "title": "Mode", "default": "timed"}, "time_limit_minutes": {"anyOf": [{"type": "integer", "exclusiveMinimum": 0.0}, {"type": "null"}], "title": "Time Limit Minutes"}, "is_shared": {"type": "boolean", "title": "Is Shared", "default": false}, "is_repetition": {"type": "boolean", "title": "Is Repetition", "default": false}, "category_ids": {"items": {"type": "integer"}, "type": "array", "title": "Category Ids"}, "state": {"type": "string", "enum": ["all", "unused", "incorrect", "bookmarked"], "title": "State", "default": "all"}, "count": {"type": "integer", "maximum": 200.0, "minimum": 1.0, "title": "Count"}, "expected_count": {"anyOf": [{"type": "integer", "minimum": 0.0}, {"type": "null"}], "title": "Expected Count"}, "difficulty": {"anyOf": [{"type": "string", "enum": ["easy", "medium", "hard"]}, {"type": "null"}], "title": "Difficulty"}, "algorithm": {"type": "string", "enum": ["random", "adaptive", "blueprint"], "title": "Algorithm", "default": "random"}, "article_ids": {"items": {"type": "integer"}, "type": "array", "title": "Article Ids"}, "tag_ids": {"items": {"type": "integer"}, "type": "array", "title": "Tag Ids"}, "system_ids": {"items": {"type": "integer"}, "type": "array", "title": "System Ids"}, "explicit_ids": {"items": {"type": "integer"}, "type": "array", "title": "Explicit Ids"}}, "type": "object", "required": ["title", "count"], "title": "GenerateTestRequest"}, "GrantIn": {"properties": {"user_id": {"type": "integer", "title": "User Id"}}, "type": "object", "required": ["user_id"], "title": "GrantIn"}, "GrantWrite": {"properties": {"kind": {"type": "string", "title": "Kind"}, "target_id": {"type": "integer", "title": "Target Id"}}, "type": "object", "required": ["kind", "target_id"], "title": "GrantWrite"}, "HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "LabInput": {"properties": {"name": {"type": "string", "maxLength": 120, "minLength": 1, "title": "Name"}, "group": {"type": "string", "maxLength": 60, "minLength": 1, "title": "Group"}, "reference_range": {"type": "string", "maxLength": 250, "minLength": 1, "title": "Reference Range"}, "units": {"type": "string", "maxLength": 80, "minLength": 1, "title": "Units"}, "age_group": {"type": "string", "maxLength": 120, "minLength": 1, "title": "Age Group"}, "specimen": {"type": "string", "maxLength": 120, "minLength": 1, "title": "Specimen"}, "source": {"type": "string", "maxLength": 500, "minLength": 1, "title": "Source"}, "source_url": {"anyOf": [{"type": "string", "maxLength": 2083, "minLength": 1, "format": "uri"}, {"type": "null"}], "title": "Source Url"}, "article_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Article Id"}, "article_section_id": {"anyOf": [{"type": "string", "maxLength": 64}, {"type": "null"}], "title": "Article Section Id"}, "is_published": {"type": "boolean", "title": "Is Published", "default": false}}, "type": "object", "required": ["name", "group", "reference_range", "units", "age_group", "specimen", "source"], "title": "LabInput"}, "LibraryGrantIn": {"properties": {"user_id": {"type": "integer", "title": "User Id"}}, "type": "object", "required": ["user_id"], "title": "LibraryGrantIn"}, "LibraryIn": {"properties": {"name": {"type": "string", "title": "Name"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}}, "type": "object", "required": ["name"], "title": "LibraryIn"}, "LiteLLMSearchRequest": {"properties": {"api_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Api Key"}, "api_base": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Api Base"}, "mode": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Mode"}}, "type": "object", "title": "LiteLLMSearchRequest"}, "LogoutRequest": {"properties": {"refresh_token": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Refresh Token"}, "everywhere": {"type": "boolean", "title": "Everywhere", "default": false}}, "type": "object", "title": "LogoutRequest"}, "ManualDeckCreate": {"properties": {"title": {"type": "string", "title": "Title"}, "category_id": {"type": "integer", "title": "Category Id"}}, "type": "object", "required": ["title", "category_id"], "title": "ManualDeckCreate"}, "ManualQuestionCreate": {"properties": {"question_text": {"type": "string", "title": "Question Text"}, "question_type": {"type": "string", "title": "Question Type", "default": "mcq"}, "options": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Options"}, "correct_answer": {"type": "string", "title": "Correct Answer"}, "explanation": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Explanation"}, "question_category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Question Category Id"}, "option_explanations": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "title": "Option Explanations"}, "key_points": {"anyOf": [{"items": {}, "type": "array"}, {"type": "null"}], "title": "Key Points"}, "attending_tip": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Attending Tip"}, "difficulty": {"anyOf": [{"type": "string", "enum": ["easy", "medium", "hard"]}, {"type": "null"}], "title": "Difficulty"}, "image_path": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Path"}, "explanation_image_path": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Explanation Image Path"}}, "type": "object", "required": ["question_text", "correct_answer"], "title": "ManualQuestionCreate"}, "MediaUpdate": {"properties": {"title": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Title"}, "caption": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Caption"}, "alt_text": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Alt Text"}, "source": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Source"}, "source_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Source Url"}, "license": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "License"}, "overlay": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "title": "Overlay"}, "modality": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Modality"}, "library_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Library Id"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}, "tags": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Tags"}}, "type": "object", "title": "MediaUpdate"}, "MintRequest": {"properties": {"quiz_id": {"type": "integer", "title": "Quiz Id"}, "question_ids": {"items": {"type": "integer"}, "type": "array", "title": "Question Ids"}}, "type": "object", "required": ["quiz_id"], "title": "MintRequest", "description": "Which questions of a session to put in a link."}, "MoveQuestions": {"properties": {"question_ids": {"items": {"type": "integer"}, "type": "array", "minItems": 1, "title": "Question Ids"}, "to_block_id": {"type": "integer", "title": "To Block Id"}}, "type": "object", "required": ["question_ids", "to_block_id"], "title": "MoveQuestions"}, "PlaceFigure": {"properties": {"asset_id": {"type": "integer", "title": "Asset Id"}, "section_id": {"type": "string", "title": "Section Id"}, "replace": {"type": "boolean", "title": "Replace", "default": false}}, "type": "object", "required": ["asset_id", "section_id"], "title": "PlaceFigure"}, "PlanUpdate": {"properties": {"name": {"anyOf": [{"type": "string", "maxLength": 200, "minLength": 1}, {"type": "null"}], "title": "Name"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "exam_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Exam Id"}, "sort_order": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Sort Order"}, "is_published": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Published"}}, "type": "object", "title": "PlanUpdate"}, "PlanWrite": {"properties": {"name": {"type": "string", "maxLength": 200, "minLength": 1, "title": "Name"}, "slug": {"type": "string", "maxLength": 120, "minLength": 1, "title": "Slug"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "kind": {"type": "string", "title": "Kind", "default": "set"}, "exam_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Exam Id"}, "sort_order": {"type": "integer", "title": "Sort Order", "default": 100}, "is_published": {"type": "boolean", "title": "Is Published", "default": true}}, "type": "object", "required": ["name", "slug"], "title": "PlanWrite"}, "PracticeIn": {"properties": {"message_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Message Id"}, "title": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Title"}}, "type": "object", "title": "PracticeIn", "description": "Which turn to practise. Absent means the latest answer in the thread."}, "PreparedSessionRequest": {"properties": {"count": {"anyOf": [{"type": "integer", "maximum": 200.0, "minimum": 1.0}, {"type": "null"}], "title": "Count"}, "mode": {"type": "string", "enum": ["timed", "learning"], "title": "Mode", "default": "learning"}, "title": {"anyOf": [{"type": "string", "maxLength": 200}, {"type": "null"}], "title": "Title"}}, "type": "object", "title": "PreparedSessionRequest", "description": "Accepting a prepared session, optionally at a different length."}, "ProgressSave": {"properties": {"quiz_id": {"type": "integer", "title": "Quiz Id"}, "attempt_id": {"type": "integer", "title": "Attempt Id"}, "answers": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Answers"}, "hints": {"anyOf": [{"items": {"type": "integer"}, "type": "array"}, {"type": "null"}], "title": "Hints"}, "current_idx": {"type": "integer", "title": "Current Idx"}, "mode": {"type": "string", "title": "Mode"}, "voice": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Voice"}, "time_left": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Time Left"}, "started_at": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Started At"}, "total_time": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Total Time"}, "suspended": {"type": "boolean", "title": "Suspended", "default": false}}, "type": "object", "required": ["quiz_id", "attempt_id", "answers", "current_idx", "mode"], "title": "ProgressSave"}, "QCatCreate": {"properties": {"name": {"type": "string", "maxLength": 200, "minLength": 1, "title": "Name"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "parent_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Parent Id"}}, "type": "object", "required": ["name"], "title": "QCatCreate"}, "QCatResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "name": {"type": "string", "title": "Name"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "parent_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Parent Id"}, "breadcrumbs": {"items": {"additionalProperties": true, "type": "object"}, "type": "array", "title": "Breadcrumbs"}, "question_count": {"type": "integer", "title": "Question Count", "default": 0}}, "type": "object", "required": ["id", "name", "description"], "title": "QCatResponse"}, "QuestionEdit": {"properties": {"question_text": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Question Text"}, "question_type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Question Type"}, "options": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Options"}, "correct_answer": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Correct Answer"}, "explanation": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Explanation"}, "question_category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Question Category Id"}, "additional_category_ids": {"anyOf": [{"items": {"type": "integer"}, "type": "array"}, {"type": "null"}], "title": "Additional Category Ids"}, "option_explanations": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "title": "Option Explanations"}, "key_points": {"anyOf": [{"items": {}, "type": "array"}, {"type": "null"}], "title": "Key Points"}, "attending_tip": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Attending Tip"}, "difficulty": {"anyOf": [{"type": "string", "enum": ["easy", "medium", "hard"]}, {"type": "null"}], "title": "Difficulty"}, "image_path": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Path"}, "explanation_image_path": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Explanation Image Path"}}, "type": "object", "title": "QuestionEdit"}, "QuestionNoteIn": {"properties": {"content": {"type": "string", "maxLength": 8000, "title": "Content", "default": ""}}, "type": "object", "title": "QuestionNoteIn"}, "QuestionResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "question_text": {"type": "string", "title": "Question Text"}, "question_type": {"type": "string", "title": "Question Type"}, "options": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Options"}, "image_path": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Path"}, "question_category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Question Category Id"}, "difficulty": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Difficulty"}, "category_breadcrumbs": {"items": {"additionalProperties": true, "type": "object"}, "type": "array", "title": "Category Breadcrumbs"}, "figures": {"items": {"additionalProperties": true, "type": "object"}, "type": "array", "title": "Figures"}}, "type": "object", "required": ["id", "question_text", "question_type", "options"], "title": "QuestionResponse"}, "QuestionWithAnswer": {"properties": {"id": {"type": "integer", "title": "Id"}, "question_text": {"type": "string", "title": "Question Text"}, "question_type": {"type": "string", "title": "Question Type"}, "options": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Options"}, "image_path": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Path"}, "question_category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Question Category Id"}, "difficulty": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Difficulty"}, "category_breadcrumbs": {"items": {"additionalProperties": true, "type": "object"}, "type": "array", "title": "Category Breadcrumbs"}, "figures": {"items": {"additionalProperties": true, "type": "object"}, "type": "array", "title": "Figures"}, "correct_answer": {"type": "string", "title": "Correct Answer"}, "explanation": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Explanation"}, "explanation_image_path": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Explanation Image Path"}, "page_reference": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Page Reference"}, "option_explanations": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "title": "Option Explanations"}, "key_points": {"anyOf": [{"items": {}, "type": "array"}, {"type": "null"}], "title": "Key Points"}}, "type": "object", "required": ["id", "question_text", "question_type", "options", "correct_answer", "explanation", "page_reference"], "title": "QuestionWithAnswer"}, "QuizCreate": {"properties": {"section_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Section Id"}, "title": {"type": "string", "title": "Title"}, "mode": {"type": "string", "title": "Mode", "default": "timed"}, "time_limit_minutes": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Time Limit Minutes"}, "model_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Model Id"}, "question_category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Question Category Id"}, "extraction_mode": {"type": "string", "title": "Extraction Mode", "default": "standard"}}, "type": "object", "required": ["title"], "title": "QuizCreate"}, "QuizDetail": {"properties": {"id": {"type": "integer", "title": "Id"}, "section_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Section Id"}, "user_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "User Id"}, "title": {"type": "string", "title": "Title"}, "questions_count": {"type": "integer", "title": "Questions Count"}, "mode": {"type": "string", "title": "Mode"}, "time_limit_minutes": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Time Limit Minutes"}, "skipped_questions": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Skipped Questions"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}, "created_at": {"type": "string", "format": "date-time", "title": "Created At"}, "deleted_at": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}], "title": "Deleted At"}, "is_published": {"type": "integer", "title": "Is Published", "default": 1}, "is_shared": {"type": "integer", "title": "Is Shared", "default": 0}, "questions_per_attempt": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Questions Per Attempt"}, "max_attempts": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Max Attempts"}, "attempt_mode": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Attempt Mode"}, "share_token": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Share Token"}, "questions": {"items": {"$ref": "#/components/schemas/QuestionResponse"}, "type": "array", "title": "Questions", "default": []}}, "type": "object", "required": ["id", "title", "questions_count", "mode", "time_limit_minutes", "created_at"], "title": "QuizDetail"}, "QuizResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "section_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Section Id"}, "user_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "User Id"}, "title": {"type": "string", "title": "Title"}, "questions_count": {"type": "integer", "title": "Questions Count"}, "mode": {"type": "string", "title": "Mode"}, "time_limit_minutes": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Time Limit Minutes"}, "skipped_questions": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Skipped Questions"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}, "created_at": {"type": "string", "format": "date-time", "title": "Created At"}, "deleted_at": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}], "title": "Deleted At"}, "is_published": {"type": "integer", "title": "Is Published", "default": 1}, "is_shared": {"type": "integer", "title": "Is Shared", "default": 0}, "questions_per_attempt": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Questions Per Attempt"}, "max_attempts": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Max Attempts"}, "attempt_mode": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Attempt Mode"}, "share_token": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Share Token"}}, "type": "object", "required": ["id", "title", "questions_count", "mode", "time_limit_minutes", "created_at"], "title": "QuizResponse"}, "QuizReview": {"properties": {"id": {"type": "integer", "title": "Id"}, "section_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Section Id"}, "user_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "User Id"}, "title": {"type": "string", "title": "Title"}, "questions_count": {"type": "integer", "title": "Questions Count"}, "mode": {"type": "string", "title": "Mode"}, "time_limit_minutes": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Time Limit Minutes"}, "skipped_questions": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Skipped Questions"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}, "created_at": {"type": "string", "format": "date-time", "title": "Created At"}, "deleted_at": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}], "title": "Deleted At"}, "is_published": {"type": "integer", "title": "Is Published", "default": 1}, "is_shared": {"type": "integer", "title": "Is Shared", "default": 0}, "questions_per_attempt": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Questions Per Attempt"}, "max_attempts": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Max Attempts"}, "attempt_mode": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Attempt Mode"}, "share_token": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Share Token"}, "questions": {"items": {"$ref": "#/components/schemas/QuestionWithAnswer"}, "type": "array", "title": "Questions", "default": []}}, "type": "object", "required": ["id", "title", "questions_count", "mode", "time_limit_minutes", "created_at"], "title": "QuizReview"}, "QuizStats": {"properties": {"quiz_id": {"type": "integer", "title": "Quiz Id"}, "quiz_title": {"type": "string", "title": "Quiz Title"}, "attempts_count": {"type": "integer", "title": "Attempts Count"}, "best_score": {"type": "number", "title": "Best Score"}, "latest_score": {"type": "number", "title": "Latest Score"}, "average_score": {"type": "number", "title": "Average Score"}}, "type": "object", "required": ["quiz_id", "quiz_title", "attempts_count", "best_score", "latest_score", "average_score"], "title": "QuizStats"}, "QuizUpdate": {"properties": {"title": {"type": "string", "title": "Title"}}, "type": "object", "required": ["title"], "title": "QuizUpdate"}, "RatingRequest": {"properties": {"rating": {"type": "integer", "title": "Rating"}}, "type": "object", "required": ["rating"], "title": "RatingRequest"}, "RefreshRequest": {"properties": {"refresh_token": {"type": "string", "title": "Refresh Token"}}, "type": "object", "required": ["refresh_token"], "title": "RefreshRequest"}, "ReplyIn": {"properties": {"reply": {"anyOf": [{"type": "string", "maxLength": 2000}, {"type": "null"}], "title": "Reply"}, "status": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Status"}}, "type": "object", "title": "ReplyIn"}, "ResetConfirm": {"properties": {"confirm": {"type": "string", "title": "Confirm"}}, "type": "object", "required": ["confirm"], "title": "ResetConfirm"}, "ResultEdit": {"properties": {"exam_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Exam Id"}, "exam_name": {"anyOf": [{"type": "string", "maxLength": 160}, {"type": "null"}], "title": "Exam Name"}, "sat_on": {"type": "string", "format": "date", "title": "Sat On"}, "kind": {"type": "string", "title": "Kind", "default": "score"}, "score": {"anyOf": [{"type": "number"}, {"type": "null"}], "title": "Score"}, "score_max": {"anyOf": [{"type": "number"}, {"type": "null"}], "title": "Score Max"}, "percentile": {"anyOf": [{"type": "number"}, {"type": "null"}], "title": "Percentile"}, "passed": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Passed"}, "note": {"anyOf": [{"type": "string", "maxLength": 2000}, {"type": "null"}], "title": "Note"}}, "type": "object", "required": ["sat_on"], "title": "ResultEdit", "description": "The same rules. An edit replaces the result, so a half-changed row\ncannot end up as a score with no maximum."}, "ResultIn": {"properties": {"exam_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Exam Id"}, "exam_name": {"anyOf": [{"type": "string", "maxLength": 160}, {"type": "null"}], "title": "Exam Name"}, "sat_on": {"type": "string", "format": "date", "title": "Sat On"}, "kind": {"type": "string", "title": "Kind", "default": "score"}, "score": {"anyOf": [{"type": "number"}, {"type": "null"}], "title": "Score"}, "score_max": {"anyOf": [{"type": "number"}, {"type": "null"}], "title": "Score Max"}, "percentile": {"anyOf": [{"type": "number"}, {"type": "null"}], "title": "Percentile"}, "passed": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Passed"}, "note": {"anyOf": [{"type": "string", "maxLength": 2000}, {"type": "null"}], "title": "Note"}}, "type": "object", "required": ["sat_on"], "title": "ResultIn"}, "RoleWrite": {"properties": {"role": {"type": "string", "title": "Role"}}, "type": "object", "required": ["role"], "title": "RoleWrite"}, "SectionCreate": {"properties": {"name": {"type": "string", "title": "Name"}, "start_page": {"type": "integer", "title": "Start Page"}, "end_page": {"type": "integer", "title": "End Page"}}, "type": "object", "required": ["name", "start_page", "end_page"], "title": "SectionCreate"}, "SectionNoteIn": {"properties": {"content": {"type": "string", "maxLength": 8000, "title": "Content", "default": ""}}, "type": "object", "title": "SectionNoteIn"}, "SectionResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "document_id": {"type": "integer", "title": "Document Id"}, "name": {"type": "string", "title": "Name"}, "start_page": {"type": "integer", "title": "Start Page"}, "end_page": {"type": "integer", "title": "End Page"}}, "type": "object", "required": ["id", "document_id", "name", "start_page", "end_page"], "title": "SectionResponse"}, "SsoExchangeRequest": {"properties": {"code": {"type": "string", "maxLength": 200, "minLength": 8, "title": "Code"}, "refresh": {"type": "boolean", "title": "Refresh", "default": false}, "device": {"anyOf": [{"type": "string", "maxLength": 120}, {"type": "null"}], "title": "Device"}}, "type": "object", "required": ["code"], "title": "SsoExchangeRequest", "description": "The one-time code the SSO redirect leaves in the address bar."}, "StatusIn": {"properties": {"status": {"type": "string", "title": "Status"}}, "type": "object", "required": ["status"], "title": "StatusIn"}, "TTSRequest": {"properties": {"text": {"type": "string", "title": "Text"}, "voice": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Voice"}}, "type": "object", "required": ["text"], "title": "TTSRequest"}, "TTSVoiceSearchRequest": {"properties": {"provider": {"type": "string", "title": "Provider"}, "api_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Api Key"}, "region": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Region"}}, "type": "object", "required": ["provider"], "title": "TTSVoiceSearchRequest"}, "TagQuestions": {"properties": {"question_ids": {"items": {"type": "integer"}, "type": "array", "maxItems": 500, "minItems": 1, "title": "Question Ids"}}, "type": "object", "required": ["question_ids"], "title": "TagQuestions"}, "TagUpdate": {"properties": {"name": {"anyOf": [{"type": "string", "maxLength": 200, "minLength": 1}, {"type": "null"}], "title": "Name"}, "parent_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Parent Id"}, "sort_order": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Sort Order"}}, "type": "object", "title": "TagUpdate"}, "TagWrite": {"properties": {"name": {"type": "string", "maxLength": 200, "minLength": 1, "title": "Name"}, "type": {"type": "string", "enum": ["subject", "disease", "keyword", "system"], "title": "Type"}, "parent_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Parent Id"}, "sort_order": {"type": "integer", "title": "Sort Order", "default": 100}}, "type": "object", "required": ["name", "type"], "title": "TagWrite"}, "TermIn": {"properties": {"term": {"type": "string", "maxLength": 120, "minLength": 1, "title": "Term"}, "definition": {"type": "string", "maxLength": 400, "minLength": 1, "title": "Definition"}, "kind": {"type": "string", "title": "Kind", "default": "define"}, "article_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Article Id"}, "source": {"anyOf": [{"type": "string", "maxLength": 60}, {"type": "null"}], "title": "Source"}}, "type": "object", "required": ["term", "definition"], "title": "TermIn"}, "TermOut": {"properties": {"id": {"type": "integer", "title": "Id"}, "key": {"type": "string", "title": "Key"}, "term": {"type": "string", "title": "Term"}, "definition": {"type": "string", "title": "Definition"}, "kind": {"type": "string", "title": "Kind"}, "article_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Article Id"}, "article_slug": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Article Slug"}, "source": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Source"}}, "type": "object", "required": ["id", "key", "term", "definition", "kind"], "title": "TermOut"}, "Token": {"properties": {"access_token": {"type": "string", "title": "Access Token"}, "token_type": {"type": "string", "title": "Token Type", "default": "bearer"}, "expires_in": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Expires In"}, "refresh_token": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Refresh Token"}}, "type": "object", "required": ["access_token"], "title": "Token"}, "UserResponse": {"properties": {"id": {"type": "integer", "title": "Id"}, "email": {"type": "string", "title": "Email"}, "name": {"type": "string", "title": "Name"}, "role": {"type": "string", "title": "Role"}, "is_unthrottled": {"type": "integer", "title": "Is Unthrottled", "default": 0}, "created_at": {"type": "string", "format": "date-time", "title": "Created At"}, "has_password": {"type": "boolean", "title": "Has Password", "default": false}, "is_moderator": {"type": "boolean", "title": "Is Moderator", "default": false}, "is_admin": {"type": "boolean", "title": "Is Admin", "default": false}}, "type": "object", "required": ["id", "email", "name", "role", "created_at"], "title": "UserResponse"}, "UserUpdateMe": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}}, "type": "object", "title": "UserUpdateMe"}, "UserUpdateRole": {"properties": {"role": {"type": "string", "title": "Role"}}, "type": "object", "required": ["role"], "title": "UserUpdateRole"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}, "app__routers__articles__FigureIn": {"properties": {"section_id": {"type": "string", "maxLength": 64, "minLength": 1, "title": "Section Id"}, "spec": {"additionalProperties": true, "type": "object", "title": "Spec"}, "replace": {"type": "boolean", "title": "Replace", "default": false}}, "type": "object", "required": ["section_id", "spec"], "title": "FigureIn", "description": "A diagram to render into one section of this article.\n\nThe spec shape and its caps are `services/diagram`'s \u2014 see\n`diagram.check()`. Nothing here decides what a good figure is."}, "app__routers__questions__FigureIn": {"properties": {"media_id": {"type": "integer", "title": "Media Id"}, "role": {"type": "string", "enum": ["stem", "explanation"], "title": "Role", "default": "stem"}, "label": {"anyOf": [{"type": "string", "maxLength": 80}, {"type": "null"}], "title": "Label"}, "caption": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Caption"}}, "type": "object", "required": ["media_id"], "title": "FigureIn"}}, "securitySchemes": {"OAuth2PasswordBearer": {"type": "oauth2", "flows": {"password": {"scopes": {}, "tokenUrl": "/api/auth/login"}}}}}}
